goletter / hyperf-mail
The Hyperf Mail package.
Requires
- php: >=8.0
- ext-json: *
- ext-swoole: >=4.5
- hyperf-ext/contract: ~2.2.0
- hyperf/async-queue: ^3.1
- hyperf/command: ^3.1
- hyperf/config: ^3.1
- hyperf/devtool: ^3.1
- hyperf/di: ^3.1
- hyperf/event: ^3.1
- hyperf/filesystem: ^3.1
- hyperf/framework: ^3.1
- hyperf/translation: ^3.1
- hyperf/view: ^3.1
- pudongping/hyperf-wise-locksmith: ^3.0
- symfony/mailer: ^6.0.5
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- hyperf/testing: ^3.1
- mockery/mockery: ^1.0
- phpstan/phpstan: ^0.12
- swoole/ide-helper: dev-master
Suggests
- hyperf/guzzle: Required to use the mail driver (^2.0).
- symfony/amazon-mailer: Required to use the AWS SES mail driver.
- symfony/google-mailer: Required to use the Gmail mail driver.
- symfony/mailchimp-mailer: Required to use the Mailchimp mail driver.
- symfony/mailgun-mailer: Required to use the Mailgun mail driver.
- symfony/mailjet-mailer: Required to use the Mailjet mail driver.
- symfony/oh-my-smtp-mailer: Required to use the OhMySMTP mail driver.
- symfony/postmark-mailer: Required to use the Postmark mail driver.
- symfony/sendgrid-mailer: Required to use the SendGrid mail driver.
- symfony/sendinblue-mailer: Required to use the Sendinblue mail driver.
Provides
None
Conflicts
None
Replaces
None
README
基于 Symfony Mailer 的 Hyperf 邮件组件,API 风格接近 Laravel Mailable。
支持 SMTP、Gmail、Mailgun、Postmark、AWS SES、Mandrill、阿里云 DM、Resend、sendmail、log 等驱动。
运行环境
- PHP >= 8.0
- Hyperf >= 3.1
- Swoole >= 4.5
安装
composer require goletter/hyperf-mail
发布配置:
php bin/hyperf.php vendor:publish goletter/hyperf-mail
配置写入 config/autoload/mail.php。改完 .env 后需重启 Hyperf 进程。
API 类驱动(Mailgun、Postmark 等)通常还需:
composer require hyperf/guzzle
快速开始
MAIL_MAILER=smtp MAIL_FROM_ADDRESS=noreply@example.com MAIL_FROM_NAME="Example" MAIL_SMTP_HOST=smtp.example.com MAIL_SMTP_PORT=587 MAIL_SMTP_ENCRYPTION=tls MAIL_SMTP_USERNAME=you@example.com MAIL_SMTP_PASSWORD=your-password
php bin/hyperf.php gen:mail OrderShipped
use App\Mail\OrderShipped; use Goletter\Mail\Mail; Mail::to($user)->send(new OrderShipped($order));
驱动配置
默认驱动由 MAIL_MAILER / mail.default 决定。多数云服务使用 DSN;SMTP 可用离散配置或 DSN。
SMTP(默认)
无需额外 Composer 依赖。
离散配置(推荐)
MAIL_MAILER=smtp MAIL_SMTP_HOST=smtp.hostinger.com MAIL_SMTP_PORT=587 MAIL_SMTP_ENCRYPTION=tls MAIL_SMTP_USERNAME=you@example.com MAIL_SMTP_PASSWORD=your-password
MAIL_SMTP_ENCRYPTION |
含义 | 常用端口 |
|---|---|---|
tls |
STARTTLS(推荐) | 587 |
ssl |
隐式 SSL/TLS | 465 |
留空 / null |
不强制加密 | 25 |
对应 mail.mailers.smtp:
'smtp' => [ 'host' => env('MAIL_SMTP_HOST'), 'port' => (int) env('MAIL_SMTP_PORT', 587), 'encryption' => env('MAIL_SMTP_ENCRYPTION', 'tls'), // tls | ssl | null 'username' => env('MAIL_SMTP_USERNAME'), 'password' => env('MAIL_SMTP_PASSWORD'), 'dsn' => env('MAIL_SMTP_DSN'), // 可选,设置后优先生效 ],
完整 DSN(可选)
设置 MAIL_SMTP_DSN 后忽略 host/port 等离散项:
MAIL_SMTP_DSN=smtp://you%40example.com:password@smtp.hostinger.com:587 # 或 ssl/465 MAIL_SMTP_DSN=smtps://you%40example.com:password@smtp.hostinger.com:465
用户名、密码中的
@、:、/等请先 URL 编码(@→%40)。
Gmail
composer require symfony/google-mailer
MAIL_MAILER=gmail MAIL_GMAIL_DSN=gmail+smtp://you%40gmail.com:your-app-password@default
需开启两步验证,并使用应用专用密码。Symfony 建议仅用于开发/测试。
Mailgun
composer require symfony/mailgun-mailer hyperf/guzzle
MAIL_MAILER=mailgun MAIL_MAILGUN_DSN=mailgun+api://KEY:DOMAIN@default # 或 mailgun+https://KEY:DOMAIN@default # 或 mailgun+smtp://USERNAME:PASSWORD@default
Postmark
composer require symfony/postmark-mailer hyperf/guzzle
MAIL_MAILER=postmark MAIL_POSTMARK_DSN=postmark+api://KEY@default # 或 postmark+smtp://ID@default
AWS SES
composer require symfony/amazon-mailer
MAIL_MAILER=aws_ses MAIL_AWS_SES_DSN=ses+api://ACCESS_KEY:SECRET_KEY@default # 或 ses+https://ACCESS_KEY:SECRET_KEY@default # 或 ses+smtp://USERNAME:PASSWORD@default
Mandrill
MAIL_MAILER=mandrill MAIL_MANDRILL_DSN=mandrill+api://KEY@default
阿里云 DM
composer require alibabacloud/dm:^1.8
MAIL_MAILER=aliyun_dm MAIL_ALIYUN_DM_ACCESS_KEY_ID= MAIL_ALIYUN_DM_ACCESS_SECRET= MAIL_ALIYUN_DM_REGION_ID= MAIL_ALIYUN_DM_CLICK_TRACE=0
'aliyun_dm' => [ 'transport' => \Goletter\Mail\Transport\AliyunDmTransport::class, 'options' => [ 'access_key_id' => env('MAIL_ALIYUN_DM_ACCESS_KEY_ID'), 'access_secret' => env('MAIL_ALIYUN_DM_ACCESS_SECRET'), 'region_id' => env('MAIL_ALIYUN_DM_REGION_ID'), 'click_trace' => env('MAIL_ALIYUN_DM_CLICK_TRACE', '0'), ], ],
仅支持事务类邮件,不支持批量。
Resend
composer require resend/resend-php:^0.22.0
MAIL_MAILER=resend MAIL_RESEND_ACCESS_KEY_ID=
'resend' => [ 'transport' => \Goletter\Mail\Transport\ResendTransport::class, 'options' => [ 'access_key_id' => env('MAIL_RESEND_ACCESS_KEY_ID'), ], ],
sendmail / log
'sendmail' => [ 'dsn' => 'sendmail://default', ], 'log' => [ 'transport' => \Goletter\Mail\Transport\LogTransport::class, 'options' => [ 'name' => 'mail.local', 'group' => 'default', ], ],
log 驱动不真正发信,只写入日志,适合本地开发。
全局发件人 / 统一收件人
'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 'name' => env('MAIL_FROM_NAME', 'Example'), ], // 可选:全局 reply-to 'reply_to' => [ 'address' => 'reply@example.com', 'name' => 'Support', ], // 可选:本地调试时,所有邮件改发到同一地址 'to' => [ 'address' => 'dev@example.com', 'name' => 'Dev', ],
编写 Mailable
php bin/hyperf.php gen:mail OrderShipped
类默认生成在 app/Mail。在 build() 中配置主题、正文、发件人与附件。
完整示例
<?php declare(strict_types=1); namespace App\Mail; use Goletter\Mail\Mailable; class OrderShipped extends Mailable { public function __construct( public string $userName, ) { } public function build(): void { $this->subject('订单已发货') // HTML 与纯文本可同时设置,会组成 multipart 邮件 ->htmlBody(sprintf( '<h1>Hello, %s</h1><p>您的订单已发货。</p>', htmlspecialchars($this->userName, ENT_QUOTES, 'UTF-8') )) ->textBody(sprintf('Hello, %s — 您的订单已发货。', $this->userName)); } }
发送:
use App\Mail\OrderShipped; use Goletter\Mail\Mail; Mail::to('user@example.com')->send(new OrderShipped('Yong'));
发件人
public function build(): void { $this->from('example@example.com', 'App Name') ->subject('订单已发货'); }
未调用 from() 时使用 mail.from 全局配置。
正文内容
可用 视图模板 或 直接写字符串。HTML 与纯文本可只设其一,也可同时设置(推荐同时设置,兼容不支持 HTML 的客户端)。
直接写正文
public function build(): void { $this->subject('通知') ->htmlBody('<p>Hello</p>') ->textBody('Hello'); // 可选;与 htmlBody 并存时为 multipart,不是附件 }
视图模板
依赖 hyperf/view(本包已引入)。以 Blade 为例:
public function build(): void { $this->subject('订单已发货') ->htmlView('emails.orders.shipped') ->textView('emails.orders.shipped_plain'); // 可选 }
视图数据
public属性会自动注入视图- 或用
with([...])手动传参(配合protected/private属性)
public function __construct(public Order $order) {} public function build(): void { $this->htmlView('emails.orders.shipped') ->with([ 'orderName' => $this->order->name, 'orderPrice' => $this->order->price, ]); }
<div>Price: {{ $order->price }}</div>
指定 Mailer
未指定时使用 mail.default(即 MAIL_MAILER)。可用两种方式覆盖:
// 发送时指定 Mail::mailer('postmark')->to($user)->send(new OrderShipped($order)); // 或在 Mailable 内指定 public function build(): void { $this->mailer('smtp') ->subject('订单已发货') ->htmlView('emails.orders.shipped'); }
附件
$this->htmlView('emails.orders.shipped') ->attach('/path/to/file', ['as' => 'name.pdf', 'mime' => 'application/pdf']) ->attachData($pdfBytes, 'name.pdf', ['mime' => 'application/pdf']) ->attachFromDefaultStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']) ->attachFromStorage('s3', '/path/to/file', 'name.zip', ['mime' => 'application/zip']);
文件系统附件依赖 hyperf/filesystem(本包已引入,需发布并配置)。
内联图片
模板中可直接使用 $message:
<img src="{{ $message->embed($pathToImage) }}"> <img src="{{ $message->embedData($data, $name) }}">
纯文本模板不要使用 $message 内联附件。
自定义 Symfony Email
public function build(): void { $this->htmlView('emails.orders.shipped'); $this->withEmail(function (\Symfony\Component\Mime\Email $message) { $message->getHeaders()->addTextHeader('X-Custom', 'value'); }); }
发送邮件
use Goletter\Mail\Mail; Mail::to($request->user())->send(new OrderShipped($order)); Mail::to($user) ->cc($moreUsers) ->bcc($evenMoreUsers) ->send(new OrderShipped($order)); // 指定 mailer(见上方「指定 Mailer」) Mail::mailer('postmark') ->to($user) ->send(new OrderShipped($order));
to / cc / bcc 支持:邮箱字符串、字符串数组、Goletter\Contract\HasMailAddress 实例或其集合。
遍历多个收件人时,请为每人新建 Mailable(to() 会累加收件人):
foreach (['a@example.com', 'b@example.com'] as $recipient) { Mail::to($recipient)->send(new OrderShipped($order)); }
队列
依赖 hyperf/async-queue,需先配置队列。
Mail::to($user)->queue(new OrderShipped($order)); Mail::to($user)->queue(new OrderShipped($order), 'emails'); // 指定队列 Mail::to($user)->later(new OrderShipped($order), 300); // 延迟秒数,单位与驱动一致 Mail::to($user)->later(new OrderShipped($order), 300, 'emails');
实现 HyperfExt\Contract\ShouldQueue 后,即使调用 send() 也会入队:
use HyperfExt\Contract\ShouldQueue; use Goletter\Mail\Mailable; class OrderShipped extends Mailable implements ShouldQueue { public string $queue = 'default'; }
渲染 / 预览
$html = Mail::render(new InvoicePaid($invoice)); // 或 $html = (new InvoicePaid($invoice))->render();
路由或控制器中直接 return new InvoicePaid($invoice); 可在浏览器预览。
本地化
Mail::to($user)->locale('es')->send(new OrderShipped($order));
模型实现 Goletter\Contract\HasLocalePreference 并返回 getPreferredLocale() 后,向该模型发信会自动使用其语言,无需再调 locale()。
本地开发
log驱动:MAIL_MAILER=log,邮件写入日志- 统一收件人:配置
mail.to,所有外发改到同一地址 - Mailtrap 等:用真实 SMTP 发到测试邮箱
事件
| 事件 | 时机 |
|---|---|
Goletter\Mail\Events\MailMessageSending |
真正发送前 |
Goletter\Mail\Events\MailMessageSent |
发送完成后 |
队列入队时不会触发;仅在实际发送时触发。
常见问题
- 改
.env后必须重启进程 - 若曾发布过旧版
mail.php,请对照publish/mail.php补全字段,或重新vendor:publish - SMTP 用户名一般是完整邮箱,以服务商文档为准
- 包名与发布命令均为
goletter/hyperf-mail(不是goletter/mail) htmlBody()+textBody()(或htmlView+textView)会组成 multipart 邮件;纯文本不是附件,附件请用attach/attachData- 未调用
mailer()/Mail::mailer()时使用MAIL_MAILER默认驱动,无需也不应提前访问$mailer属性