邮件
- Introduction
- Generating Mailables
- Writing Mailables
- Markdown Mailables
- Sending Mail
- Rendering Mailables
- Localizing Mailables
- Testing
- Mail and Local Development
- Events
- Custom Transports
介绍
发送电子邮件并非复杂。Laravel 提供了简洁的电子邮件 API,由广受欢迎的Symfony Mailer组件提供支持。Laravel 和 Symfony Mailer 提供了通过 SMTP、Mailgun、Postmark、Resend、Amazon SES 等发送电子邮件的驱动程序sendmail
,让您可以快速开始通过您选择的本地或云端服务发送邮件。
配置
Laravel 的电子邮件服务可以通过应用程序的config/mail.php
配置文件进行配置。此文件中配置的每个邮件程序可能都有其独特的配置,甚至独特的“传输”,从而允许您的应用程序使用不同的电子邮件服务来发送特定的电子邮件。例如,您的应用程序可以使用 Postmark 发送事务性邮件,同时使用 Amazon SES 发送批量邮件。
在你的配置文件中mail
,你会发现一个mailers
配置数组。该数组包含 Laravel 支持的每个主要邮件驱动程序/传输的示例配置条目,而default
配置值决定了当你的应用程序需要发送电子邮件时默认使用哪个邮件程序。
驾驶员/交通运输前提条件
基于 API 的驱动程序(例如 Mailgun、Postmark、Resend 和 MailerSend)通常比通过 SMTP 服务器发送邮件更简单、更快捷。我们建议您尽可能使用这些驱动程序。
Mailgun 驱动程序
要使用 Mailgun 驱动程序,请通过 Composer 安装 Symfony 的 Mailgun Mailer 传输:
1composer require symfony/mailgun-mailer symfony/http-client
接下来,您需要在应用程序的config/mail.php
配置文件中进行两处更改。首先,将默认邮件程序设置为mailgun
:
1'default' => env('MAIL_MAILER', 'mailgun'),
其次,将以下配置数组添加到您的数组中mailers
:
1'mailgun' => [2 'transport' => 'mailgun',3 // 'client' => [4 // 'timeout' => 5,5 // ],6],
配置应用程序的默认邮件程序后,将以下选项添加到config/services.php
配置文件中:
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),5 'scheme' => 'https',6],
如果您没有使用美国Mailgun 区域,您可以在配置文件中定义您所在区域的端点services
:
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),5 'scheme' => 'https',6],
邮戳驱动程序
要使用Postmark驱动程序,请通过 Composer 安装 Symfony 的 Postmark Mailer 传输:
1composer require symfony/postmark-mailer symfony/http-client
接下来,将default
应用程序config/mail.php
配置文件中的选项设置为postmark
。配置应用程序的默认邮件程序后,请确保config/services.php
配置文件包含以下选项:
1'postmark' => [2 'token' => env('POSTMARK_TOKEN'),3],
如果你想指定某个邮件程序使用的 Postmark 消息流,可以将配置选项添加到邮件程序的配置数组中。此配置数组可以在应用程序的配置文件message_stream_id
中找到:config/mail.php
1'postmark' => [2 'transport' => 'postmark',3 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),4 // 'client' => [5 // 'timeout' => 5,6 // ],7],
这样,您还可以设置具有不同消息流的多个 Postmark 邮件程序。
重新发送驱动程序
要使用Resend驱动程序,请通过 Composer 安装 Resend 的 PHP SDK:
1composer require resend/resend-php
接下来,将default
应用程序config/mail.php
配置文件中的选项设置为resend
。配置应用程序的默认邮件程序后,请确保config/services.php
配置文件包含以下选项:
1'resend' => [2 'key' => env('RESEND_KEY'),3],
SES 驱动程序
要使用 Amazon SES 驱动程序,您必须先安装适用于 PHP 的 Amazon AWS SDK。您可以通过 Composer 软件包管理器安装此库:
1composer require aws/aws-sdk-php
接下来,将配置文件default
中的选项设置为,并验证配置文件包含以下选项:config/mail.php
ses
config/services.php
1'ses' => [2 'key' => env('AWS_ACCESS_KEY_ID'),3 'secret' => env('AWS_SECRET_ACCESS_KEY'),4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),5],
要通过会话令牌使用 AWS临时凭证token
,您可以向应用程序的 SES 配置添加一个密钥:
1'ses' => [2 'key' => env('AWS_ACCESS_KEY_ID'),3 'secret' => env('AWS_SECRET_ACCESS_KEY'),4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),5 'token' => env('AWS_SESSION_TOKEN'),6],
要与 SES 的订阅管理功能进行交互,您可以返回邮件消息的headersX-Ses-List-Management-Options
方法返回的数组中的标题:
1/** 2 * Get the message headers. 3 */ 4public function headers(): Headers 5{ 6 return new Headers( 7 text: [ 8 'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic', 9 ],10 );11}
如果您想定义Laravel在发送电子邮件时应传递给 AWS SDK方法的其他选项,您可以在配置中定义一个数组:SendEmail
options
ses
1'ses' => [ 2 'key' => env('AWS_ACCESS_KEY_ID'), 3 'secret' => env('AWS_SECRET_ACCESS_KEY'), 4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 5 'options' => [ 6 'ConfigurationSetName' => 'MyConfigurationSet', 7 'EmailTags' => [ 8 ['Name' => 'foo', 'Value' => 'bar'], 9 ],10 ],11],
MailerSend 驱动程序
MailerSend是一个事务性邮件和短信服务,它为 Laravel 维护了基于 API 的邮件驱动程序。包含该驱动程序的软件包可以通过 Composer 包管理器安装:
1composer require mailersend/laravel-driver
安装软件包后,将MAILERSEND_API_KEY
环境变量添加到应用程序.env
文件中。此外,MAIL_MAILER
环境变量应定义为mailersend
:
1MAIL_MAILER=mailersend2MAIL_FROM_ADDRESS=app@yourdomain.com3MAIL_FROM_NAME="App Name"4 5MAILERSEND_API_KEY=your-api-key
最后,将 MailerSend 添加到mailers
应用程序config/mail.php
配置文件中的数组中:
1'mailersend' => [2 'transport' => 'mailersend',3],
要了解有关 MailerSend 的更多信息,包括如何使用托管模板,请查阅MailerSend 驱动程序文档。
故障转移配置
有时,您配置的用于发送应用程序邮件的外部服务可能会出现故障。在这种情况下,定义一个或多个备用邮件投递配置会很有用,以便在主投递驱动程序出现故障时使用。
为此,您应该在应用程序的mail
配置文件中定义一个使用该failover
传输方式的邮件程序。应用程序failover
邮件程序的配置数组应包含一个数组,mailers
该数组引用了已配置邮件程序的投递顺序:
1'mailers' => [ 2 'failover' => [ 3 'transport' => 'failover', 4 'mailers' => [ 5 'postmark', 6 'mailgun', 7 'sendmail', 8 ], 9 ],10 11 // ...12],
default
一旦定义了故障转移邮件程序,您就应该通过将其名称指定为应用程序配置文件中的配置键的值,将该邮件程序设置为应用程序使用的默认邮件程序mail
:
1'default' => env('MAIL_MAILER', 'failover'),
循环配置
传输roundrobin
机制允许您将邮件负载分散到多个邮件程序中。首先,请在应用程序的mail
配置文件中定义一个使用该roundrobin
传输机制的邮件程序。应用程序roundrobin
邮件程序的配置数组中应包含一个数组,mailers
该数组引用了用于投递的已配置邮件程序:
1'mailers' => [ 2 'roundrobin' => [ 3 'transport' => 'roundrobin', 4 'mailers' => [ 5 'ses', 6 'postmark', 7 ], 8 ], 9 10 // ...11],
default
一旦定义了循环邮件程序,您就应该通过将其名称指定为应用程序配置文件中的配置键的值,将此邮件程序设置为应用程序使用的默认邮件程序mail
:
1'default' => env('MAIL_MAILER', 'roundrobin'),
循环传输机制会从已配置的邮件程序列表中随机选择一个邮件程序,然后切换到下一个可用的邮件程序发送后续邮件。与failover
有助于实现高可用性的传输机制不同,roundrobin
循环传输机制提供负载均衡。
生成 Mailable
在构建 Laravel 应用程序时,应用程序发送的每种类型的电子邮件都表示为一个“可邮寄”类。这些类存储在app/Mail
目录中。如果您在应用程序中没有看到此目录,请不要担心,因为它会在您使用 Artisan 命令创建第一个可邮寄类时为您生成make:mail
:
1php artisan make:mail OrderShipped
编写 Mailable
生成可邮寄类后,请将其打开,以便我们探索其内容。可邮寄类的配置可以通过多种方法完成,包括envelope
、content
和attachments
方法。
该envelope
方法返回一个Illuminate\Mail\Mailables\Envelope
对象,该对象定义消息的主题,有时还定义收件人。该content
方法返回一个Illuminate\Mail\Mailables\Content
对象,该对象定义用于生成消息内容的Blade 模板。
配置发送方
使用信封
首先,让我们了解如何配置电子邮件的发件人。或者,换句话说,就是电子邮件的发件人。有两种方法可以配置发件人。第一种,您可以在邮件信封上指定“发件人”地址:
1use Illuminate\Mail\Mailables\Address; 2use Illuminate\Mail\Mailables\Envelope; 3 4/** 5 * Get the message envelope. 6 */ 7public function envelope(): Envelope 8{ 9 return new Envelope(10 from: new Address('jeffrey@example.com', 'Jeffrey Way'),11 subject: 'Order Shipped',12 );13}
如果您愿意,您还可以指定一个replyTo
地址:
1return new Envelope(2 from: new Address('jeffrey@example.com', 'Jeffrey Way'),3 replyTo: [4 new Address('taylor@example.com', 'Taylor Otwell'),5 ],6 subject: 'Order Shipped',7);
使用全局from
地址
但是,如果您的应用程序对所有电子邮件都使用相同的“发件人”地址,那么将其添加到您生成的每个可邮寄类中可能会很麻烦。相反,您可以在config/mail.php
配置文件中指定一个全局“发件人”地址。如果在可邮寄类中未指定其他“发件人”地址,则将使用此地址:
1'from' => [2 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),3 'name' => env('MAIL_FROM_NAME', 'Example'),4],
此外,您可以在config/mail.php
配置文件中定义全局“reply_to”地址:
1'reply_to' => ['address' => 'example@example.com', 'name' => 'App Name'],
配置视图
在 mailable 类的content
方法中,你可以定义view
,或者在渲染电子邮件内容时应使用的模板。由于每封电子邮件通常都使用Blade 模板来渲染其内容,因此在构建电子邮件 HTML 时,你可以充分利用 Blade 模板引擎的全部功能和便利性:
1/**2 * Get the message content definition.3 */4public function content(): Content5{6 return new Content(7 view: 'mail.orders.shipped',8 );9}
您可能希望创建一个resources/views/emails
目录来存放所有电子邮件模板;但是,您可以随意将它们放置在resources/views
目录中的任何位置。
纯文本电子邮件
如果您想定义纯文本版本的电子邮件,可以在创建邮件Content
定义时指定纯文本模板。与view
参数一样,text
参数也应为用于呈现邮件内容的模板名称。您可以自由定义邮件的 HTML 版本或纯文本版本:
1/** 2 * Get the message content definition. 3 */ 4public function content(): Content 5{ 6 return new Content( 7 view: 'mail.orders.shipped', 8 text: 'mail.orders.shipped-text' 9 );10}
为了清楚起见,该html
参数可以用作该view
参数的别名:
1return new Content(2 html: 'mail.orders.shipped',3 text: 'mail.orders.shipped-text'4);
查看数据
通过公共属性
通常,您需要将一些数据传递给视图,以便在渲染电子邮件的 HTML 时使用。您可以通过两种方式将数据提供给视图。首先,在可邮寄类中定义的任何公共属性都将自动提供给视图。例如,您可以将数据传递给可邮寄类的构造函数,并将该数据设置为该类中定义的公共属性:
1<?php 2 3namespace App\Mail; 4 5use App\Models\Order; 6use Illuminate\Bus\Queueable; 7use Illuminate\Mail\Mailable; 8use Illuminate\Mail\Mailables\Content; 9use Illuminate\Queue\SerializesModels;10 11class OrderShipped extends Mailable12{13 use Queueable, SerializesModels;14 15 /**16 * Create a new message instance.17 */18 public function __construct(19 public Order $order,20 ) {}21 22 /**23 * Get the message content definition.24 */25 public function content(): Content26 {27 return new Content(28 view: 'mail.orders.shipped',29 );30 }31}
一旦数据被设置为公共属性,它将自动在您的视图中可用,因此您可以像访问 Blade 模板中的任何其他数据一样访问它:
1<div>2 Price: {{ $order->price }}3</div>
通过with
参数:
如果您想在将电子邮件数据发送到模板之前自定义其格式,可以通过Content
定义的with
参数手动将数据传递给视图。通常,您仍会通过 mailable 类的构造函数传递数据;但是,您应该将此数据设置为protected
或private
属性,这样数据就不会自动提供给模板:
1<?php 2 3namespace App\Mail; 4 5use App\Models\Order; 6use Illuminate\Bus\Queueable; 7use Illuminate\Mail\Mailable; 8use Illuminate\Mail\Mailables\Content; 9use Illuminate\Queue\SerializesModels;10 11class OrderShipped extends Mailable12{13 use Queueable, SerializesModels;14 15 /**16 * Create a new message instance.17 */18 public function __construct(19 protected Order $order,20 ) {}21 22 /**23 * Get the message content definition.24 */25 public function content(): Content26 {27 return new Content(28 view: 'mail.orders.shipped',29 with: [30 'orderName' => $this->order->name,31 'orderPrice' => $this->order->price,32 ],33 );34 }35}
一旦数据传递给with
方法,它将自动在您的视图中可用,因此您可以像访问 Blade 模板中的任何其他数据一样访问它:
1<div>2 Price: {{ $orderPrice }}3</div>
附件
要向电子邮件添加附件,您需要将附件添加到消息方法返回的数组中。首先,您可以通过向类提供的方法attachments
提供文件路径来添加附件:fromPath
Attachment
1use Illuminate\Mail\Mailables\Attachment; 2 3/** 4 * Get the attachments for the message. 5 * 6 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 7 */ 8public function attachments(): array 9{10 return [11 Attachment::fromPath('/path/to/file'),12 ];13}
as
将文件附加到消息时,您还可以使用和方法指定附件的显示名称和/或 MIME 类型withMime
:
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromPath('/path/to/file')10 ->as('name.pdf')11 ->withMime('application/pdf'),12 ];13}
从磁盘附加文件
如果您已将文件存储在其中一个文件系统磁盘上,则可以使用附件方法将其附加到电子邮件中fromStorage
:
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromStorage('/path/to/file'),10 ];11}
当然,您也可以指定附件的名称和 MIME 类型:
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromStorage('/path/to/file')10 ->as('name.pdf')11 ->withMime('application/pdf'),12 ];13}
fromStorageDisk
如果需要指定默认磁盘以外的存储磁盘,可以使用该方法:
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromStorageDisk('s3', '/path/to/file')10 ->as('name.pdf')11 ->withMime('application/pdf'),12 ];13}
原始数据附件
attachment方法fromData
可用于将原始字节字符串作为附件附加。例如,如果您在内存中生成了一个 PDF 文件,并希望将其附加到电子邮件中而不写入磁盘,则可以使用此方法。该fromData
方法接受一个闭包,该闭包解析原始数据字节以及应分配给附件的名称:
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromData(fn () => $this->pdf, 'Report.pdf')10 ->withMime('application/pdf'),11 ];12}
内联附件
在电子邮件中嵌入内联图片通常比较麻烦;不过,Laravel 提供了一种便捷的方法将图片附加到电子邮件中。要嵌入内联图片,请在电子邮件模板中的变量embed
上使用方法。Laravel 会自动将该变量提供给所有电子邮件模板,因此您无需手动传递它:$message
$message
1<body>2 Here is an image:3 4 <img src="{{ $message->embed($pathToImage) }}">5</body>
$message
由于纯文本消息不使用内联附件,因此
该变量在纯文本消息模板中不可用。
嵌入原始数据附件
如果您已有要嵌入到电子邮件模板中的原始图像数据字符串,则可以embedData
对变量调用该方法$message
。调用该embedData
方法时,您需要提供一个要分配给嵌入图像的文件名:
1<body>2 Here is an image from raw data:3 4 <img src="{{ $message->embedData($data, 'example-image.jpg') }}">5</body>
可附着对象
虽然通过简单的字符串路径将文件附加到消息通常就足够了,但很多情况下,应用程序中的可附加实体是用类来表示的。例如,如果您的应用程序要将照片附加到消息中,则应用程序可能还包含一个表示该照片的模型。在这种情况下,将模型直接传递给方法Photo
岂不是更方便?可附加对象正是您实现这一点的途径。Photo
attach
首先,Illuminate\Contracts\Mail\Attachable
在可附加到消息的对象上实现接口。此接口要求您的类定义一个toMailAttachment
返回Illuminate\Mail\Attachment
实例的方法:
1<?php 2 3namespace App\Models; 4 5use Illuminate\Contracts\Mail\Attachable; 6use Illuminate\Database\Eloquent\Model; 7use Illuminate\Mail\Attachment; 8 9class Photo extends Model implements Attachable10{11 /**12 * Get the attachable representation of the model.13 */14 public function toMailAttachment(): Attachment15 {16 return Attachment::fromPath('/path/to/file');17 }18}
attachments
一旦定义了可附加对象,您就可以在构建电子邮件消息时从方法中返回该对象的实例:
1/**2 * Get the attachments for the message.3 *4 * @return array<int, \Illuminate\Mail\Mailables\Attachment>5 */6public function attachments(): array7{8 return [$this->photo];9}
当然,附件数据可能存储在远程文件存储服务(例如 Amazon S3)上。因此,Laravel 还允许你从存储在应用程序文件系统磁盘上的数据生成附件实例:
1// Create an attachment from a file on your default disk...2return Attachment::fromStorage($this->path);3 4// Create an attachment from a file on a specific disk...5return Attachment::fromStorageDisk('backblaze', $this->path);
此外,您还可以通过内存中的数据创建附件实例。为此,请为该fromData
方法提供一个闭包。该闭包应返回表示附件的原始数据:
1return Attachment::fromData(fn () => $this->content, 'Photo Name');
Laravel 还提供了其他方法供你自定义附件。例如,你可以使用as
和withMime
方法来自定义文件的名称和 MIME 类型:
1return Attachment::fromPath('/path/to/file')2 ->as('Photo Name')3 ->withMime('image/jpeg');
标题
有时您可能需要在发送的邮件中附加额外的标头。例如,您可能需要设置自定义Message-Id
或其他任意文本标头。
为此,请headers
在您的 mailable 上定义一个方法。该headers
方法应返回一个Illuminate\Mail\Mailables\Headers
实例。此类接受messageId
、references
和text
参数。当然,您可以只提供特定消息所需的参数:
1use Illuminate\Mail\Mailables\Headers; 2 3/** 4 * Get the message headers. 5 */ 6public function headers(): Headers 7{ 8 return new Headers( 9 messageId: 'custom-message-id@example.com',10 references: ['previous-message@example.com'],11 text: [12 'X-Custom-Header' => 'Custom Value',13 ],14 );15}
标签和元数据
一些第三方电子邮件提供商(例如 Mailgun 和 Postmark)支持邮件“标签”和“元数据”,它们可用于对应用程序发送的邮件进行分组和跟踪。您可以通过以下方式为电子邮件添加标签和元数据Envelope
:
1use Illuminate\Mail\Mailables\Envelope; 2 3/** 4 * Get the message envelope. 5 * 6 * @return \Illuminate\Mail\Mailables\Envelope 7 */ 8public function envelope(): Envelope 9{10 return new Envelope(11 subject: 'Order Shipped',12 tags: ['shipment'],13 metadata: [14 'order_id' => $this->order->id,15 ],16 );17}
如果您的应用程序使用的是 Mailgun 驱动程序,您可以查阅 Mailgun 的文档以获取有关标签和元数据的更多信息。同样,您也可以查阅 Postmark 文档以获取有关其对标签和元数据的支持的更多信息。
如果您的应用程序使用 Amazon SES 发送电子邮件,您应该使用该方法将SES“标签”metadata
附加到消息中。
自定义 Symfony 消息
Laravel 的邮件功能由 Symfony Mailer 提供支持。Laravel 允许您注册自定义回调,这些回调将在发送消息之前通过 Symfony Message 实例调用。这使您有机会在发送消息之前对其进行深度自定义。为此,请在您的定义中定义一个using
参数:Envelope
1use Illuminate\Mail\Mailables\Envelope; 2use Symfony\Component\Mime\Email; 3 4/** 5 * Get the message envelope. 6 */ 7public function envelope(): Envelope 8{ 9 return new Envelope(10 subject: 'Order Shipped',11 using: [12 function (Email $message) {13 // ...14 },15 ]16 );17}
Markdown 邮件
Markdown 邮件消息允许您利用邮件中预建的邮件通知模板和组件。由于这些消息是用 Markdown 编写的,因此 Laravel 能够为这些消息渲染美观且响应迅速的 HTML 模板,同时还能自动生成纯文本副本。
生成 Markdown 邮件
要使用相应的 Markdown 模板生成可邮寄内容,您可以使用Artisan 命令--markdown
的选项make:mail
:
1php artisan make:mail OrderShipped --markdown=mail.orders.shipped
Content
然后,在其方法内配置 mailable 定义时content
,使用markdown
参数而不是view
参数:
1use Illuminate\Mail\Mailables\Content; 2 3/** 4 * Get the message content definition. 5 */ 6public function content(): Content 7{ 8 return new Content( 9 markdown: 'mail.orders.shipped',10 with: [11 'url' => $this->orderUrl,12 ],13 );14}
编写 Markdown 消息
Markdown mailables 结合使用了 Blade 组件和 Markdown 语法,让您可以轻松构建邮件消息,同时利用 Laravel 预构建的电子邮件 UI 组件:
1<x-mail::message> 2# Order Shipped 3 4Your order has been shipped! 5 6<x-mail::button :url="$url"> 7View Order 8</x-mail::button> 9 10Thanks,<br>11{{ config('app.name') }}12</x-mail::message>
撰写 Markdown 电子邮件时,请勿使用过多的缩进。根据 Markdown 标准,Markdown 解析器会将缩进的内容渲染为代码块。
按钮组件
按钮组件渲染一个居中的按钮链接。该组件接受两个参数:url
和一个可选的color
。支持的颜色包括primary
、success
和error
。您可以根据需要向消息中添加任意数量的按钮组件:
1<x-mail::button :url="$url" color="success">2View Order3</x-mail::button>
面板组件
面板组件会将指定的文本块渲染到一个面板中,该面板的背景颜色与消息的其余部分略有不同。这可以让你更好地吸引用户对指定文本块的注意力:
1<x-mail::panel>2This is the panel content.3</x-mail::panel>
表格组件
表格组件允许您将 Markdown 表格转换为 HTML 表格。该组件接受 Markdown 表格作为其内容。表格列对齐支持使用默认的 Markdown 表格对齐语法:
1<x-mail::table>2| Laravel | Table | Example |3| ------------- | :-----------: | ------------: |4| Col 2 is | Centered | $10 |5| Col 3 is | Right-Aligned | $20 |6</x-mail::table>
自定义组件
你可以将所有 Markdown 邮件组件导出到你自己的应用程序中,以便进行自定义。要导出组件,请使用vendor:publish
Artisan 命令发布laravel-mail
资产标签:
1php artisan vendor:publish --tag=laravel-mail
此命令会将 Markdown 邮件组件发布到resources/views/vendor/mail
目录。该mail
目录将包含一个html
和一个text
目录,每个目录包含每个可用组件的对应表示。您可以随意自定义这些组件。
自定义 CSS
导出组件后,resources/views/vendor/mail/html/themes
目录将包含一个default.css
文件。您可以自定义此文件中的 CSS,这些样式将自动转换为 Markdown 邮件 HTML 格式中的内联 CSS 样式。
如果您想为 Laravel 的 Markdown 组件构建一个全新的主题,您可以在该html/themes
目录中放置一个 CSS 文件。命名并保存 CSS 文件后,更新theme
应用程序config/mail.php
配置文件的选项以匹配新主题的名称。
要为单个邮件定制主题,您可以将$theme
邮件类的属性设置为发送该邮件时应使用的主题的名称。
发送邮件
要发送消息,请使用Facadeto
上的方法。该方法接受电子邮件地址、用户实例或用户集合作为参数。如果您传递一个对象或对象集合,邮件程序将自动使用它们的和属性来确定电子邮件的收件人,因此请确保您的对象上存在这些属性。指定收件人后,您可以将可邮寄类的实例传递给该方法:Mail
to
email
name
send
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Mail\OrderShipped; 6use App\Models\Order; 7use Illuminate\Http\RedirectResponse; 8use Illuminate\Http\Request; 9use Illuminate\Support\Facades\Mail;10 11class OrderShipmentController extends Controller12{13 /**14 * Ship the given order.15 */16 public function store(Request $request): RedirectResponse17 {18 $order = Order::findOrFail($request->order_id);19 20 // Ship the order...21 22 Mail::to($request->user())->send(new OrderShipped($order));23 24 return redirect('/orders');25 }26}
发送消息时,您不仅可以指定“收件人”,还可以通过链接相应的方法来设置“收件人”、“抄送”和“密送”收件人:
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->send(new OrderShipped($order));
循环遍历收件人
有时,你可能需要通过遍历收件人/电子邮件地址数组来将邮件对象发送给收件人列表。但是,由于该to
方法会将电子邮件地址附加到邮件对象的收件人列表中,因此循环的每次迭代都会向之前的每个收件人发送一封电子邮件。因此,你应该始终为每个收件人重新创建邮件对象实例:
1foreach (['taylor@example.com', 'dries@example.com'] as $recipient) {2 Mail::to($recipient)->send(new OrderShipped($order));3}
通过特定邮件程序发送邮件
默认情况下,Laravel 会使用default
应用程序mail
配置文件中配置的邮件程序发送电子邮件。但是,你可以使用以下mailer
方法,通过特定的邮件程序配置发送消息:
1Mail::mailer('postmark')2 ->to($request->user())3 ->send(new OrderShipped($order));
排队邮件
将邮件消息放入队列
由于发送电子邮件会对应用程序的响应时间产生负面影响,许多开发人员选择将电子邮件排队以便在后台发送。Laravel 使用其内置的统一队列 API轻松实现此操作。要将邮件消息放入队列,请在指定邮件收件人后使用外观层上queue
的方法:Mail
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->queue(new OrderShipped($order));
此方法会自动将作业推送到队列,以便在后台发送消息。使用此功能前,您需要配置队列。
延迟消息队列
如果您希望延迟发送队列中的电子邮件,可以使用该later
方法。该later
方法的第一个参数是一个DateTime
实例,用于指示邮件的发送时间:
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->later(now()->addMinutes(10), new OrderShipped($order));
推送到特定队列
由于使用该make:mail
命令生成的所有可邮寄类都使用了该Illuminate\Bus\Queueable
特征,因此您可以在任何可邮寄类实例上调用onQueue
和onConnection
方法,从而允许您为消息指定连接和队列名称:
1$message = (new OrderShipped($order))2 ->onConnection('sqs')3 ->onQueue('emails');4 5Mail::to($request->user())6 ->cc($moreUsers)7 ->bcc($evenMoreUsers)8 ->queue($message);
默认排队
如果你希望某个可邮寄类始终处于队列中,则可以ShouldQueue
在该类上实现该契约。这样,即使你send
在发送邮件时调用该方法,该可邮寄类仍然会处于队列中,因为它实现了该契约:
1use Illuminate\Contracts\Queue\ShouldQueue;2 3class OrderShipped extends Mailable implements ShouldQueue4{5 // ...6}
队列邮件和数据库事务
当队列中的可邮寄对象在数据库事务中被调度时,它们可能会在数据库事务提交之前就被队列处理。发生这种情况时,您在数据库事务期间对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,在事务中创建的任何模型或数据库记录可能都不存在于数据库中。如果您的可邮寄对象依赖于这些模型,则在处理发送队列可邮寄对象的作业时可能会发生意外错误。
如果您的队列连接的after_commit
配置选项设置为,您仍然可以通过在发送邮件消息时false
调用该方法来指示在提交所有打开的数据库事务之后应分派特定的排队邮件:afterCommit
1Mail::to($request->user())->send(2 (new OrderShipped($order))->afterCommit()3);
或者,您可以afterCommit
从 mailable 的构造函数中调用该方法:
1<?php 2 3namespace App\Mail; 4 5use Illuminate\Bus\Queueable; 6use Illuminate\Contracts\Queue\ShouldQueue; 7use Illuminate\Mail\Mailable; 8use Illuminate\Queue\SerializesModels; 9 10class OrderShipped extends Mailable implements ShouldQueue11{12 use Queueable, SerializesModels;13 14 /**15 * Create a new message instance.16 */17 public function __construct()18 {19 $this->afterCommit();20 }21}
渲染 Mailable
有时你可能希望捕获可邮寄对象的 HTML 内容而不发送它。为此,你可以调用render
可邮寄对象的相应方法。此方法将以字符串形式返回已评估的可邮寄对象 HTML 内容:
1use App\Mail\InvoicePaid;2use App\Models\Invoice;3 4$invoice = Invoice::find(1);5 6return (new InvoicePaid($invoice))->render();
在浏览器中预览邮件
在设计可邮寄对象的模板时,可以像典型的 Blade 模板一样在浏览器中快速预览渲染后的可邮寄对象,这非常方便。因此,Laravel 允许你直接从路由闭包或控制器返回任何可邮寄对象。返回的可邮寄对象将在浏览器中渲染并显示,让你可以快速预览其设计,而无需将其发送到实际的电子邮件地址:
1Route::get('/mailable', function () {2 $invoice = App\Models\Invoice::find(1);3 4 return new App\Mail\InvoicePaid($invoice);5});
本地化邮件
Laravel 允许您在请求的当前语言环境以外的语言环境中发送邮件,并且如果邮件已排队,甚至会记住该语言环境。
为了实现这一点,Mail
外观组件提供了一个locale
设置所需语言的方法。当 mailable 模板被评估时,应用程序将切换到此语言环境,评估完成后,应用程序将恢复到先前的语言环境:
1Mail::to($request->user())->locale('es')->send(2 new OrderShipped($order)3);
用户首选语言环境
有时,应用程序会存储每个用户的首选语言环境。通过HasLocalePreference
在一个或多个模型上实现此契约,您可以指示 Laravel 在发送邮件时使用此存储的语言环境:
1use Illuminate\Contracts\Translation\HasLocalePreference; 2 3class User extends Model implements HasLocalePreference 4{ 5 /** 6 * Get the user's preferred locale. 7 */ 8 public function preferredLocale(): string 9 {10 return $this->locale;11 }12}
locale
实现该接口后,Laravel 会在向模型发送邮件和通知时自动使用首选语言环境。因此,使用此接口时无需调用该方法:
1Mail::to($request->user())->send(new OrderShipped($order));
测试
测试可邮寄内容
Laravel 提供了多种方法来检查你的 mailable 结构。此外,Laravel 还提供了几种便捷的方法来测试你的 mailable 是否包含你期望的内容。这些方法是:assertSeeInHtml
、assertDontSeeInHtml
、assertSeeInOrderInHtml
、assertSeeInText
、assertDontSeeInText
、assertSeeInOrderInText
、assertHasAttachment
、assertHasAttachedData
、assertHasAttachmentFromStorage
和assertHasAttachmentFromStorageDisk
。
正如您所料,“HTML”断言断言您的邮件的 HTML 版本包含给定的字符串,而“文本”断言断言您的邮件的纯文本版本包含给定的字符串:
1use App\Mail\InvoicePaid; 2use App\Models\User; 3 4test('mailable content', function () { 5 $user = User::factory()->create(); 6 7 $mailable = new InvoicePaid($user); 8 9 $mailable->assertFrom('jeffrey@example.com');10 $mailable->assertTo('taylor@example.com');11 $mailable->assertHasCc('abigail@example.com');12 $mailable->assertHasBcc('victoria@example.com');13 $mailable->assertHasReplyTo('tyler@example.com');14 $mailable->assertHasSubject('Invoice Paid');15 $mailable->assertHasTag('example-tag');16 $mailable->assertHasMetadata('key', 'value');17 18 $mailable->assertSeeInHtml($user->email);19 $mailable->assertSeeInHtml('Invoice Paid');20 $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);21 22 $mailable->assertSeeInText($user->email);23 $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);24 25 $mailable->assertHasAttachment('/path/to/file');26 $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));27 $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);28 $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);29 $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);30});
1use App\Mail\InvoicePaid; 2use App\Models\User; 3 4public function test_mailable_content(): void 5{ 6 $user = User::factory()->create(); 7 8 $mailable = new InvoicePaid($user); 9 10 $mailable->assertFrom('jeffrey@example.com');11 $mailable->assertTo('taylor@example.com');12 $mailable->assertHasCc('abigail@example.com');13 $mailable->assertHasBcc('victoria@example.com');14 $mailable->assertHasReplyTo('tyler@example.com');15 $mailable->assertHasSubject('Invoice Paid');16 $mailable->assertHasTag('example-tag');17 $mailable->assertHasMetadata('key', 'value');18 19 $mailable->assertSeeInHtml($user->email);20 $mailable->assertSeeInHtml('Invoice Paid');21 $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);22 23 $mailable->assertSeeInText($user->email);24 $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);25 26 $mailable->assertHasAttachment('/path/to/file');27 $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));28 $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);29 $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);30 $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);31}
测试 Mailable 发送
我们建议将邮件内容的测试与断言指定邮件已“发送”给特定用户的测试分开进行。通常,邮件内容与您正在测试的代码无关,只需断言 Laravel 已收到发送指定邮件的指示即可。
你可以使用Mail
Facade 的fake
方法来阻止邮件的发送。调用Mail
Facade 的fake
方法后,你可以断言邮件对象已被指示发送给用户,甚至可以检查邮件对象收到的数据:
1<?php 2 3use App\Mail\OrderShipped; 4use Illuminate\Support\Facades\Mail; 5 6test('orders can be shipped', function () { 7 Mail::fake(); 8 9 // Perform order shipping...10 11 // Assert that no mailables were sent...12 Mail::assertNothingSent();13 14 // Assert that a mailable was sent...15 Mail::assertSent(OrderShipped::class);16 17 // Assert a mailable was sent twice...18 Mail::assertSent(OrderShipped::class, 2);19 20 // Assert a mailable was sent to an email address...21 Mail::assertSent(OrderShipped::class, 'example@laravel.com');22 23 // Assert a mailable was sent to multiple email addresses...24 Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);25 26 // Assert a mailable was not sent...27 Mail::assertNotSent(AnotherMailable::class);28 29 // Assert 3 total mailables were sent...30 Mail::assertSentCount(3);31});
1<?php 2 3namespace Tests\Feature; 4 5use App\Mail\OrderShipped; 6use Illuminate\Support\Facades\Mail; 7use Tests\TestCase; 8 9class ExampleTest extends TestCase10{11 public function test_orders_can_be_shipped(): void12 {13 Mail::fake();14 15 // Perform order shipping...16 17 // Assert that no mailables were sent...18 Mail::assertNothingSent();19 20 // Assert that a mailable was sent...21 Mail::assertSent(OrderShipped::class);22 23 // Assert a mailable was sent twice...24 Mail::assertSent(OrderShipped::class, 2);25 26 // Assert a mailable was sent to an email address...27 Mail::assertSent(OrderShipped::class, 'example@laravel.com');28 29 // Assert a mailable was sent to multiple email addresses...30 Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);31 32 // Assert a mailable was not sent...33 Mail::assertNotSent(AnotherMailable::class);34 35 // Assert 3 total mailables were sent...36 Mail::assertSentCount(3);37 }38}
如果您正在后台排队等待邮件投递,则应使用assertQueued
以下方法代替assertSent
:
1Mail::assertQueued(OrderShipped::class);2Mail::assertNotQueued(OrderShipped::class);3Mail::assertNothingQueued();4Mail::assertQueuedCount(3);
你可以将闭包传递给assertSent
、assertNotSent
、assertQueued
或assertNotQueued
方法,以断言已发送的邮件对象是否通过了给定的“真实性测试”。如果至少有一个邮件对象通过了给定的真实性测试,则断言成功:
1Mail::assertSent(function (OrderShipped $mail) use ($order) {2 return $mail->order->id === $order->id;3});
当调用Mail
外观的断言方法时,提供的闭包接受的可邮寄实例会公开有用的方法来检查可邮寄:
1Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) {2 return $mail->hasTo($user->email) &&3 $mail->hasCc('...') &&4 $mail->hasBcc('...') &&5 $mail->hasReplyTo('...') &&6 $mail->hasFrom('...') &&7 $mail->hasSubject('...');8});
mailable 实例还包含几个用于检查 mailable 附件的有用方法:
1use Illuminate\Mail\Mailables\Attachment; 2 3Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { 4 return $mail->hasAttachment( 5 Attachment::fromPath('/path/to/file') 6 ->as('name.pdf') 7 ->withMime('application/pdf') 8 ); 9});10 11Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {12 return $mail->hasAttachment(13 Attachment::fromStorageDisk('s3', '/path/to/file')14 );15});16 17Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) {18 return $mail->hasAttachment(19 Attachment::fromData(fn () => $pdfData, 'name.pdf')20 );21});
您可能已经注意到,有两种方法可以断言邮件未发送:assertNotSent
和assertNotQueued
。有时您可能希望断言没有邮件已发送或排队。为此,您可以使用assertNothingOutgoing
和assertNotOutgoing
方法:
1Mail::assertNothingOutgoing();2 3Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) {4 return $mail->order->id === $order->id;5});
邮件和本地开发
在开发发送电子邮件的应用程序时,您可能不希望实际向实际的电子邮件地址发送电子邮件。Laravel 提供了几种在本地开发期间“禁用”实际发送电子邮件的方法。
日志驱动程序
邮件驱动程序不会直接发送您的电子邮件,而是log
会将所有电子邮件写入日志文件以供检查。通常,此驱动程序仅在本地开发期间使用。有关根据环境配置应用程序的更多信息,请参阅配置文档。
HELO/Mailtrap/Mailpit
或者,您可以使用HELO或Mailtrap等服务及其smtp
驱动程序,将电子邮件发送到一个“虚拟”邮箱,然后在真正的电子邮件客户端中查看。这种方法的好处是,您可以在 Mailtrap 的消息查看器中实际查看最终的邮件。
如果您使用的是Laravel Sail ,则可以使用Mailpit预览邮件。当 Sail 运行时,您可以通过以下方式访问 Mailpit 界面http://localhost:8025
:
使用全局to
地址
alwaysTo
最后,你可以通过调用外观层提供的方法指定一个全局的“收件人”地址。通常,此方法应该从应用程序的某个服务提供者的方法Mail
中调用:boot
1use Illuminate\Support\Facades\Mail; 2 3/** 4 * Bootstrap any application services. 5 */ 6public function boot(): void 7{ 8 if ($this->app->environment('local')) { 9 Mail::alwaysTo('taylor@example.com');10 }11}
Events
Laravel 在发送邮件时会调度两个事件。一个MessageSending
事件在邮件发送之前调度,另一个MessageSent
事件在邮件发送之后调度。请记住,这些事件是在邮件发送时调度,而不是在邮件进入队列时调度。您可以在应用程序中为这些事件创建事件监听器:
1use Illuminate\Mail\Events\MessageSending; 2// use Illuminate\Mail\Events\MessageSent; 3 4class LogMessage 5{ 6 /** 7 * Handle the event. 8 */ 9 public function handle(MessageSending $event): void10 {11 // ...12 }13}
定制运输
Laravel 包含多种邮件传输方式;然而,您可能希望编写自己的传输方式,以便通过 Laravel 不支持的其他服务来发送电子邮件。首先,定义一个继承自该类的类Symfony\Component\Mailer\Transport\AbstractTransport
。然后,在您的传输方式上实现doSend
和__toString()
方法:
1use MailchimpTransactional\ApiClient; 2use Symfony\Component\Mailer\SentMessage; 3use Symfony\Component\Mailer\Transport\AbstractTransport; 4use Symfony\Component\Mime\Address; 5use Symfony\Component\Mime\MessageConverter; 6 7class MailchimpTransport extends AbstractTransport 8{ 9 /**10 * Create a new Mailchimp transport instance.11 */12 public function __construct(13 protected ApiClient $client,14 ) {15 parent::__construct();16 }17 18 /**19 * {@inheritDoc}20 */21 protected function doSend(SentMessage $message): void22 {23 $email = MessageConverter::toEmail($message->getOriginalMessage());24 25 $this->client->messages->send(['message' => [26 'from_email' => $email->getFrom(),27 'to' => collect($email->getTo())->map(function (Address $email) {28 return ['email' => $email->getAddress(), 'type' => 'to'];29 })->all(),30 'subject' => $email->getSubject(),31 'text' => $email->getTextBody(),32 ]]);33 }34 35 /**36 * Get the string representation of the transport.37 */38 public function __toString(): string39 {40 return 'mailchimp';41 }42}
定义自定义传输后,您可以通过外观层extend
提供的方法进行注册Mail
。通常,这应该在boot
应用程序AppServiceProvider
服务提供商的方法中完成。方法$config
的闭包中会传入一个参数extend
。该参数包含应用程序配置文件中为邮件程序定义的配置数组config/mail.php
:
1use App\Mail\MailchimpTransport; 2use Illuminate\Support\Facades\Mail; 3 4/** 5 * Bootstrap any application services. 6 */ 7public function boot(): void 8{ 9 Mail::extend('mailchimp', function (array $config = []) {10 return new MailchimpTransport(/* ... */);11 });12}
一旦定义并注册了自定义传输,您就可以在应用程序的config/mail.php
配置文件中创建使用新传输的邮件程序定义:
1'mailchimp' => [2 'transport' => 'mailchimp',3 // ...4],
额外的 Symfony 传输
Laravel 支持一些现有的 Symfony 维护的邮件传输工具,例如 Mailgun 和 Postmark。然而,您可能希望扩展 Laravel 以支持其他 Symfony 维护的传输工具。您可以通过 Composer 引入必要的 Symfony 邮件程序,并在 Laravel 中注册该传输工具来实现。例如,您可以安装并注册“Brevo”(以前称为“Sendinblue”)Symfony 邮件程序:
1composer require symfony/brevo-mailer symfony/http-client
安装 Brevo 邮件程序包后,您可以将 Brevo API 凭据的条目添加到应用程序的services
配置文件中:
1'brevo' => [2 'key' => 'your-api-key',3],
接下来,你可以使用Mail
Facade 的extend
方法向 Laravel 注册传输。通常,这应该在boot
服务提供者的方法中完成:
1use Illuminate\Support\Facades\Mail; 2use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; 3use Symfony\Component\Mailer\Transport\Dsn; 4 5/** 6 * Bootstrap any application services. 7 */ 8public function boot(): void 9{10 Mail::extend('brevo', function () {11 return (new BrevoTransportFactory)->create(12 new Dsn(13 'brevo+api',14 'default',15 config('services.brevo.key')16 )17 );18 });19}
一旦你的传输被注册,你可以在应用程序的 config/mail.php 配置文件中创建一个利用新传输的邮件程序定义:
1'brevo' => [2 'transport' => 'brevo',3 // ...4],