邮件
简介
发送电子邮件不必很复杂。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 驱动程序
要使用 Postmark 驱动程序,请通过 Composer 安装 Symfony 的 Postmark Mailer 传输器
1composer require symfony/postmark-mailer symfony/http-client
接下来,将应用程序 config/mail.php
配置文件中的 default
选项设置为 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 驱动程序
要使用 Resend 驱动程序,请通过 Composer 安装 Resend 的 PHP SDK
1composer require resend/resend-php
接下来,将应用程序 config/mail.php
配置文件中的 default
选项设置为 resend
。配置应用程序的默认邮件程序后,请确保您的 config/services.php
配置文件包含以下选项
1'resend' => [2 'key' => env('RESEND_KEY'),3],
SES 驱动程序
要使用 Amazon SES 驱动程序,您必须首先安装 Amazon AWS SDK for PHP。您可以通过 Composer 包管理器安装此库
1composer require aws/aws-sdk-php
接下来,将 config/mail.php
配置文件中的 default
选项设置为 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 的 订阅管理功能,您可以在 headers
邮件消息的方法返回的数组中返回 X-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
方法的其他选项,您可以在 ses
配置中定义一个 options
数组
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,一种事务性电子邮件和 SMS 服务,为其 Laravel 维护了自己的基于 API 的邮件驱动程序。包含该驱动程序的软件包可以通过 Composer 包管理器安装
1composer require mailersend/laravel-driver
软件包安装完成后,将 MAILERSEND_API_KEY
环境变量添加到应用程序的 .env
文件中。此外,MAIL_MAILER
环境变量应定义为 mailersend
1MAIL_MAILER=mailersend3MAIL_FROM_NAME="App Name"4 5MAILERSEND_API_KEY=your-api-key
最后,将 MailerSend 添加到应用程序 config/mail.php
配置文件中的 mailers
数组中
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],
定义故障转移邮件程序后,您应该通过将邮件程序的名称指定为应用程序 mail
配置文件中 default
配置键的值,将此邮件程序设置为应用程序使用的默认邮件程序
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],
定义轮询邮件程序后,您应该通过将邮件程序的名称指定为应用程序 mail
配置文件中 default
配置键的值,将此邮件程序设置为应用程序使用的默认邮件程序
1'default' => env('MAIL_MAILER', 'roundrobin'),
轮询传输器从配置的邮件程序列表中选择一个随机邮件程序,然后为每个后续电子邮件切换到下一个可用的邮件程序。与有助于实现高可用性的 failover
传输器相比,roundrobin
传输器提供了负载均衡。
生成邮件类
在构建 Laravel 应用程序时,您的应用程序发送的每种类型的电子邮件都表示为一个“邮件类”。这些类存储在 app/Mail
目录中。如果您在应用程序中没有看到此目录,请不要担心,因为它将在您使用 make:mail
Artisan 命令创建您的第一个邮件类时为您生成
1php artisan make:mail OrderShipped
编写邮件类
生成邮件类后,打开它,以便我们可以探索其内容。邮件类配置在几个方法中完成,包括 envelope
、content
和 attachments
方法。
envelope
方法返回一个 Illuminate\Mail\Mailables\Envelope
对象,该对象定义了邮件的主题,有时还定义了收件人。content
方法返回一个 Illuminate\Mail\Mailables\Content
对象,该对象定义了将用于生成消息内容的 Blade 模板。
配置发件人
使用 Envelope
首先,让我们探索配置电子邮件的发件人。或者换句话说,电子邮件将“来自”谁。有两种方法可以配置发件人。首先,您可以在消息的信封上指定“发件人”地址
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(11 subject: 'Order Shipped',12 );13}
如果您愿意,您还可以指定 replyTo
地址
1return new Envelope(3 replyTo: [5 ],6 subject: 'Order Shipped',7);
使用全局 from
地址
但是,如果您的应用程序对其所有电子邮件都使用相同的“发件人”地址,则将其添加到您生成的每个邮件类中可能会变得很麻烦。相反,您可以在 config/mail.php
配置文件中指定全局“发件人”地址。如果在邮件类中未指定其他“发件人”地址,则将使用此地址
1'from' => [3 'name' => env('MAIL_FROM_NAME', 'Example'),4],
此外,您可以在 config/mail.php
配置文件中定义全局“回复至”地址
配置视图
在邮件类的 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
参数将数据传递到视图。通常,您仍然会通过邮件类的构造函数传递数据;但是,您应该将此数据设置为 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
方法返回的数组中。首先,您可以通过提供文件路径到 Attachment
类提供的 fromPath
方法来添加附件
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
和 withMime
方法指定附件的显示名称和/或 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::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}
原始数据附件
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 提供了一种将图像附加到电子邮件的便捷方法。要嵌入内联图像,请在您的电子邮件模板中使用 $message
变量上的 embed
方法。Laravel 会自动使 $message
变量可用于您的所有电子邮件模板,因此您无需担心手动传递它
1<body>2 Here is an image:3 4 <img src="{{ $message->embed($pathToImage) }}">5</body>
$message
变量在纯文本消息模板中不可用,因为纯文本消息不使用内联附件。
嵌入原始数据附件
如果您已经有一个想要嵌入到电子邮件模板中的原始图像数据字符串,您可以调用 $message
变量上的 embedData
方法。调用 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
方法。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(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 发送电子邮件,您应该使用 metadata
方法将 SES “标签” 附加到消息。
自定义 Symfony 消息
Laravel 的邮件功能由 Symfony Mailer 提供支持。Laravel 允许您注册自定义回调,这些回调将在发送消息之前使用 Symfony Message 实例调用。这使您有机会在消息发送之前对其进行深度自定义。要完成此操作,请在您的 Envelope
定义上定义一个 using
参数
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 模板的邮件类,您可以使用 make:mail
Artisan 命令的 --markdown
选项
1php artisan make:mail OrderShipped --markdown=mail.orders.shipped
然后,在邮件类的 content
方法中配置邮件类 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 邮件类结合使用了 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 组件构建一个全新的主题,您可以将 CSS 文件放在 html/themes
目录中。命名并保存 CSS 文件后,更新应用程序 config/mail.php
配置文件的 theme
选项以匹配新主题的名称。
要自定义单个邮件类的主题,您可以将邮件类的 $theme
属性设置为应在发送该邮件类时使用的主题名称。
发送邮件
要发送消息,请使用 Mail
外观模式 上的 to
方法。to
方法接受电子邮件地址、用户实例或用户集合。如果您传递对象或对象集合,则邮件程序将在确定电子邮件的收件人时自动使用其 email
和 name
属性,因此请确保这些属性在您的对象上可用。指定收件人后,您可以将邮件类的实例传递给 send
方法
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Http\Controllers\Controller; 6use App\Mail\OrderShipped; 7use App\Models\Order; 8use Illuminate\Http\RedirectResponse; 9use Illuminate\Http\Request;10use Illuminate\Support\Facades\Mail;11 12class OrderShipmentController extends Controller13{14 /**15 * Ship the given order.16 */17 public function store(Request $request): RedirectResponse18 {19 $order = Order::findOrFail($request->order_id);20 21 // Ship the order...22 23 Mail::to($request->user())->send(new OrderShipped($order));24 25 return redirect('/orders');26 }27}
您不仅限于在发送消息时仅指定“收件人”("to")。您可以自由地通过链接其各自的方法来设置“收件人”("to")、“抄送”("cc") 和“密件抄送”("bcc") 收件人
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->send(new OrderShipped($order));
循环处理收件人
有时,您可能需要通过迭代接收者/电子邮件地址数组,向接收者列表发送邮件。但是,由于 to
方法会将电子邮件地址附加到邮件的接收者列表,因此循环中的每次迭代都会向每个之前的接收者发送另一封电子邮件。因此,您应该始终为每个接收者重新创建邮件实例。
2 Mail::to($recipient)->send(new OrderShipped($order));3}
通过特定的邮件发送器发送邮件
默认情况下,Laravel 将使用在应用程序的 mail
配置文件中配置为 default
邮件发送器的邮件发送电子邮件。但是,您可以使用 mailer
方法使用特定的邮件发送器配置发送消息。
1Mail::mailer('postmark')2 ->to($request->user())3 ->send(new OrderShipped($order));
队列邮件
队列化邮件消息
由于发送电子邮件消息可能会对应用程序的响应时间产生负面影响,因此许多开发人员选择将电子邮件消息排队以进行后台发送。Laravel 使用其内置的 统一队列 API 使此操作变得容易。要队列化邮件消息,请在指定消息的接收者后,在 Mail
facade 上使用 queue
方法。
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
trait,因此您可以在任何邮件类实例上调用 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
方法。
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}
要了解更多关于解决这些问题的信息,请查看关于队列化作业和数据库事务的文档。
渲染邮件类
有时您可能希望在不发送邮件的情况下捕获邮件的 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
facade 提供了 locale
方法来设置所需的语言。当评估邮件的模板时,应用程序将更改为此区域设置,然后在评估完成后恢复为之前的区域设置。
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}
一旦您实现了接口,Laravel 将在向模型发送邮件和通知时自动使用首选区域设置。因此,在使用此接口时,无需调用 locale
方法。
1Mail::to($request->user())->send(new OrderShipped($order));
测试
测试邮件内容
Laravel 提供了多种方法来检查邮件的结构。此外,Laravel 还提供了几种方便的方法来测试您的邮件是否包含您期望的内容。这些方法是:assertSeeInHtml
、assertDontSeeInHtml
、assertSeeInOrderInHtml
、assertSeeInText
、assertDontSeeInText
、assertSeeInOrderInText
、assertHasAttachment
、assertHasAttachedData
、assertHasAttachmentFromStorage
和 assertHasAttachmentFromStorageDisk
。
正如您可能期望的那样,“HTML”断言断言邮件的 HTML 版本包含给定的字符串,而“text”断言断言邮件的纯文本版本包含给定的字符串。
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 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 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}
测试邮件发送
我们建议将邮件内容的测试与断言给定邮件已“发送”给特定用户的测试分开进行。通常,邮件的内容与您正在测试的代码无关,只需断言 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...22 23 // Assert a mailable was sent to multiple email addresses...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...28 29 // Assert a mailable was sent to multiple email addresses...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
facade 的断言方法时,提供的闭包接受的邮件实例公开了用于检查邮件的有用方法。
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});
邮件实例还包括几个用于检查邮件附件的有用方法。
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 界面:https://127.0.0.1:8025
。
使用全局 to
地址
最后,您可以通过调用 Mail
facade 提供的 alwaysTo
方法来指定全局“to”地址。通常,应从应用程序的服务提供商之一的 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')) {10 }11}
事件
Laravel 在发送邮件消息时调度两个事件。MessageSending
事件在消息发送之前调度,而 MessageSent
事件在消息发送之后调度。请记住,这些事件是在邮件被发送时调度的,而不是在邮件被队列化时调度的。您可以在您的应用程序中为这些事件创建事件侦听器。
1use Illuminate\Mail\Events\MessageSending; 2// use Illuminate\Mail\Events\MessageSent; 3 4class LogMessage 5{ 6 /** 7 * Handle the given 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}
一旦您定义了自定义传输方式,您可以通过 Mail
facade 提供的 extend
方法注册它。通常,这应该在应用程序的 AppServiceProvider
服务提供商的 boot
方法中完成。$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],