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