diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 1dc7cbf..914f57f 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,6 +2,7 @@
+
diff --git a/docs/Email.md b/docs/Email.md
new file mode 100644
index 0000000..4c86d1e
--- /dev/null
+++ b/docs/Email.md
@@ -0,0 +1,76 @@
+# Email | 邮件
+提供一套通用 Service 用于定制需求,和一套现成的事件/监听器用于异步发送。
+
+## 服务类
+[\Singularity\HDK\Core\Service\EmailService](../src/Service/EmailService.php)
+* 发送纯文本邮件的 `\Singularity\HDK\Core\Service\EmailService::sendText()`
+* 发送 Html 富文本邮件的 `\Singularity\HDK\Core\Service\EmailService::sendHtml()`
+
+## 事件机制
+> 这种方式无法监听邮件发送的结果!
+>
+> 如果你需要根据邮件发送的结果进行处理,
+> 请使用服务类进行自定义
+
+更好的方法可能是在事件发生后触发异步队列,
+但如果将异步队列封装在本包中,
+相当于强制引用本库的项目使用异步队列机制,
+这是我们不愿意看到的。
+所以这里的监听器只是集成了同步发送的操作。
+
+如果你想要定义更好的设计,
+你可以直接使用提供的 Service 类进行定制。
+> 或许这里还有优化空间😄
+
+### 事件
+[\Singularity\HDK\Core\Events\EmailWillSent](../src/Events/EmailWillSent.php)
+### 监听器
+[\Singularity\HDK\Core\Listener\EmailWillSentListener](../src/Listener/EmailWillSentListener.php)
+### 使用方法
+监听器已经通过 [ConfigProvider](../src/ConfigProvider.php) 机制注册,
+所以你只需要在要发送邮件的地方调度这个事件即可。
+> 这也就意味着,你无法调用这个事件,但不触发这个监听器。
+> 如果你有这样做的必要,请创建一个自己的事件。
+> 这也是为了保证功能的完整性和原子性。
+
+具体使用方法,如下代码所示:
+
+```php
+save();
+
+ // ↓
+ // 这里 dispatch(object $event) 会逐个运行监听该事件的监听器
+ $this->eventDispatcher->dispatch(new EmailWillSent(
+ target: $user->mobile,
+ subject: '注册成功通知',
+ type: 'html', // 默认 text 可以不传
+ content: <<恭喜你,注册成功!
+HTML,
+ ));
+ // ↑
+
+ return $result;
+ }
+}
+
+```
\ No newline at end of file
diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php
index 6d71981..2413615 100644
--- a/src/ConfigProvider.php
+++ b/src/ConfigProvider.php
@@ -14,6 +14,7 @@ namespace Singularity\HDK\Core;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Framework\Logger\StdoutLogger;
+use Singularity\HDK\Core\Listener\EmailWillSentListener;
class ConfigProvider
{
@@ -38,7 +39,9 @@ class ConfigProvider
'commands' => [
],
// 与 commands 类似
- 'listeners' => [],
+ 'listeners' => [
+ EmailWillSentListener::class
+ ],
// 组件默认配置文件,即执行命令后会把 source 的对应的文件复制为 destination 对应的的文件
'publish' => [
[
diff --git a/src/Events/EmailWillSent.php b/src/Events/EmailWillSent.php
new file mode 100644
index 0000000..44cb2c0
--- /dev/null
+++ b/src/Events/EmailWillSent.php
@@ -0,0 +1,50 @@
+
+ * Powered by PhpStorm
+ * Created on 2023/1/16
+ */
+
+namespace Singularity\HDK\Core\Events;
+
+/**
+ * Singularity\HDK\Core\Events\EmailWillSent@HDK-Core
+ *
+ * @author 李东云
+ * Powered by PhpStorm
+ * Created on 2023/1/16
+ *
+ * @link ../../docs/Email.md
+ */
+class EmailWillSent
+{
+ public function __construct(
+ /**
+ * @var string|string[] $target
+ */
+ public string|array $target,
+
+ /**
+ * @var non-empty-string $subject
+ */
+ public string $subject,
+
+ /**
+ * @var non-empty-string $content
+ */
+ public string $content,
+
+ /**
+ * @var string[] $cc
+ */
+ public array $cc = [],
+
+ /**
+ * @var 'text'|'html' $type
+ */
+ public string $type = 'text'
+ ) {
+ }
+}
\ No newline at end of file
diff --git a/src/Listener/EmailWillSentListener.php b/src/Listener/EmailWillSentListener.php
new file mode 100644
index 0000000..cba3297
--- /dev/null
+++ b/src/Listener/EmailWillSentListener.php
@@ -0,0 +1,94 @@
+
+ * Powered by PhpStorm
+ * Created on 2023/1/16
+ */
+
+namespace Singularity\HDK\Core\Listener;
+
+use Hyperf\Contract\ContainerInterface;
+use Hyperf\Contract\StdoutLoggerInterface;
+use Hyperf\Di\Annotation\Inject;
+use Hyperf\Event\Contract\ListenerInterface;
+use JetBrains\PhpStorm\NoReturn;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\NotFoundExceptionInterface;
+use Singularity\HDK\Core\Constants\CommonErrorCode;
+use Singularity\HDK\Core\Events\EmailWillSent;
+use Singularity\HDK\Core\Service\EmailService;
+use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
+
+/**
+ * Singularity\HDK\Core\Listener\EmailWillSentListener@HDK-Core
+ *
+ * @author 李东云
+ * Powered by PhpStorm
+ * Created on 2023/1/16
+ *
+ * @link ../../docs/Email.md
+ */
+class EmailWillSentListener implements ListenerInterface
+{
+ #[Inject]
+ private ContainerInterface $container;
+
+ /**
+ * @inheritDoc
+ */
+ public function listen(): array
+ {
+ return [
+ EmailWillSent::class,
+ ];
+ }
+
+ /**
+ * @param EmailWillSent $event
+ * @return void
+ * @throws ContainerExceptionInterface
+ * @throws NotFoundExceptionInterface
+ */
+ #[NoReturn]
+ public function process(object $event): void
+ {
+ $stdoutLogger = $this->container->get(StdoutLoggerInterface::class);
+ $emailService = $this->container->get(EmailService::class);
+ try {
+ $event->type === 'html'
+ ? $emailService->sendHtml(
+ target: $event->target,
+ subject: $event->subject,
+ html: $event->content,
+ cc: $event->cc
+ )
+ : $emailService->sendText(
+ target: $event->target,
+ subject: $event->subject,
+ text: $event->content,
+ cc: $event->cc
+ );
+
+ $stdoutLogger->info('邮件发送成功!');
+ $stdoutLogger->info("To: $event->target");
+ $stdoutLogger->info("Subject: $event->subject");
+ $stdoutLogger->debug('Content: ');
+ $stdoutLogger->debug($event->content);
+ } catch (TransportExceptionInterface $e) {
+ $code = CommonErrorCode::SERVER_MESSAGE_EMAIL_ERROR;
+ $msg = $e->getMessage();
+ if (strpos($msg, '500 Error: bad syntax')) {
+ $code = CommonErrorCode::SERVER_MESSAGE_EMAIL_NOT_FOUND;
+ $msg = CommonErrorCode::getMessage($code);
+ }
+ $stdoutLogger->alert('邮件发送失败!');
+ $stdoutLogger->error(
+ <<