邮件
介绍
发送邮件并不需要很复杂。Laravel 提供了一个由流行的 Symfony Mailer 组件驱动的简洁、简单的邮件 API。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 传输:
composer require symfony/mailgun-mailer symfony/http-client
接下来,您需要在应用程序的 config/mail.php
配置文件中进行两处更改。首先,将默认邮件程序设置为 mailgun
:
'default' => env('MAIL_MAILER', '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
头:
/**
* 获取消息头。
*/
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=mailersend
MAIL_FROM_ADDRESS=app@yourdomain.com
MAIL_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',
],
'retry_after' => 60,
],
// ...
],
定义故障转移邮件程序后,您应通过在应用程序的 mail
配置文件中将其名称指定为 default
配置键的值来将此邮件程序设置为应用程序使用的默认邮件程序:
'default' => env('MAIL_MAILER', 'failover'),
轮询配置
roundrobin
传输允许您将邮件工作负载分配到多个邮件程序。要开始,请在应用程序的 mail
配置文件中定义一个使用 roundrobin
传输的邮件程序。应用程序的 roundrobin
邮件程序的配置数组应包含一个 mailers
数组,该数组引用应用于传递的配置邮件程序:
'mailers' => [
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
// ...
],
定义轮询邮件程序后,您应通过在应用程序的 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 模板。
配置发件人
使用信封
首先,让我们探索配置电子邮件的发件人。或者换句话说,电子邮件将从谁那里发送。有两种方法可以配置发件人。首先,您可以在消息的信封上指定“发件人”地址:
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
/**
* 获取消息信封。
*/
public function envelope(): Envelope
{
return new Envelope(
from: new Address('jeffrey@example.com', 'Jeffrey Way'),
subject: 'Order Shipped',
);
}
如果您愿意,还可以指定 replyTo
地址:
return new Envelope(
from: new Address('jeffrey@example.com', 'Jeffrey Way'),
replyTo: [
new Address('taylor@example.com', 'Taylor Otwell'),
],
subject: 'Order Shipped',
);
使用全局 from
地址
但是,如果您的应用程序对其所有电子邮件使用相同的“发件人”地址,则将其添加到您生成的每个邮件类中可能会变得繁琐。相反,您可以在 config/mail.php
配置文件中指定全局“发件人”地址。如果邮件类中未指定其他“发件人”地址,则将使用此地址:
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
此外,您可以在 config/mail.php
配置文件中定义全局“reply_to”地址:
'reply_to' => [
'address' => 'example@example.com',
'name' => 'App Name',
],
配置视图
在邮件类的 content
方法中,您可以定义 view
,即在渲染电子邮件内容时应使用的模板。由于每封电子邮件通常使用 Blade 模板 来渲染其内容,因此在构建电子邮件的 HTML 时,您可以充分利用 Blade 模板引擎的强大功能和便利性:
/**
* 获取消息内容定义。
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}
NOTE
您可能希望创建一个 resources/views/mail
目录来存放所有电子邮件模板;但是,您可以自由地将它们放置在 resources/views
目录中的任何位置。
纯文本电子邮件
如果您希望定义电子邮件的纯文本版本,可以在创建消息的 Content
定义时指定纯文本模板。与 view
参数一样,text
参数应为模板名称,该模板将用于渲染电子邮件的内容。您可以自由定义消息的 HTML 和纯文本版本:
/**
* 获取消息内容定义。
*/
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;
/**
* 创建新消息实例。
*/
public function __construct(
public Order $order,
) {}
/**
* 获取消息内容定义。
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}
}
一旦数据设置为公共属性,它将自动在视图中可用,因此您可以像访问 Blade 模板中的任何其他数据一样访问它:
<div>
价格: {{ $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;
/**
* 创建新消息实例。
*/
public function __construct(
protected Order $order,
) {}
/**
* 获取消息内容定义。
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
with: [
'orderName' => $this->order->name,
'orderPrice' => $this->order->price,
],
);
}
}
一旦数据通过 with
参数传递,它将自动在您的视图中可用,因此您可以像在 Blade 模板中访问任何其他数据一样访问它:
<div>
价格: {{ $orderPrice }}
</div>
附件
要向电子邮件添加附件,您需要将附件添加到消息的 attachments
方法返回的数组中。首先,您可以通过向 Attachment
类提供文件路径来添加附件:
use Illuminate\Mail\Mailables\Attachment;
/**
* 获取消息的附件。
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file'),
];
}
在将文件附加到消息时,您还可以使用 as
和 withMime
方法指定附件的显示名称和/或 MIME 类型:
/**
* 获取消息的附件。
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}
从磁盘附加文件
如果您在某个 文件系统磁盘 上存储了文件,可以使用 fromStorage
附件方法将其附加到电子邮件中:
/**
* 获取消息的附件。
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorage('/path/to/file'),
];
}
当然,您还可以指定附件的名称和 MIME 类型:
/**
* 获取消息的附件。
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorage('/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}
如果您需要指定默认磁盘以外的存储磁盘,可以使用 fromStorageDisk
方法:
/**
* 获取消息的附件。
*
* @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
方法接受一个闭包,该闭包解析原始数据字节以及应分配给附件的名称:
/**
* 获取消息的附件。
*
* @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>
这是一个图像:
<img src="{{ $message->embed($pathToImage) }}">
</body>
WARNING
由于纯文本消息模板不使用内联附件,因此 $message
变量在纯文本消息模板中不可用。
嵌入原始数据附件
如果您已经有一个原始图像数据字符串,并希望将其嵌入到电子邮件模板中,可以在 $message
变量上调用 embedData
方法。在调用 embedData
方法时,您需要提供一个应分配给嵌入图像的文件名:
<body>
这是来自原始数据的图像:
<img src="{{ $message->embedData($data, 'example-image.jpg') }}">
</body>
可附加对象
虽然通过简单的字符串路径将文件附加到消息通常是足够的,但在许多情况下,应用程序中的可附加实体由类表示。例如,如果您的应用程序将照片附加到消息,您的应用程序可能还会有一个表示该照片的 Photo
模型。在这种情况下,直接将 Photo
模型传递给 attach
方法不是很方便吗?可附加对象允许您这样做。
要开始,请在将附加到消息的对象上实现 Illuminate\Contracts\Mail\Attachable
接口。此接口要求您的类定义一个返回 Illuminate\Mail\Attachment
实例的 toMailAttachment
方法:
<?php
namespace App\Models;
use Illuminate\Contracts\Mail\Attachable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Mail\Attachment;
class Photo extends Model implements Attachable
{
/**
* 获取模型的可附加表示。
*/
public function toMailAttachment(): Attachment
{
return Attachment::fromPath('/path/to/file');
}
}
定义可附加对象后,您可以在构建电子邮件消息时从 attachments
方法返回该对象的实例:
/**
* 获取消息的附件。
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [$this->photo];
}
当然,附件数据可以存储在远程文件存储服务(如 Amazon S3)上。因此,Laravel 还允许您从存储在应用程序的 文件系统磁盘 上的数据生成附件实例:
// 从默认磁盘上的文件创建附件...
return Attachment::fromStorage($this->path);
// 从特定磁盘上的文件创建附件...
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;
/**
* 获取消息头。
*/
public function headers(): Headers
{
return new Headers(
messageId: 'custom-message-id@example.com',
references: ['previous-message@example.com'],
text: [
'X-Custom-Header' => 'Custom Value',
],
);
}
标签和元数据
一些第三方电子邮件提供商(如 Mailgun 和 Postmark)支持消息“标签”和“元数据”,可用于分组和跟踪应用程序发送的电子邮件。您可以通过 Envelope
定义将标签和元数据添加到电子邮件消息中:
use Illuminate\Mail\Mailables\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 "tags" 附加到消息中。
自定义 Symfony 消息
Laravel 的邮件功能由 Symfony Mailer 提供支持。Laravel 允许您注册自定义回调,这些回调将在发送消息之前与 Symfony Message 实例一起调用。这使您有机会在消息发送之前对其进行深度自定义。为此,请在 Envelope
定义上定义一个 using
参数:
use Illuminate\Mail\Mailables\Envelope;
use Symfony\Component\Mime\Email;
/**
* 获取消息信封。
*/
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;
/**
* 获取消息内容定义。
*/
public function content(): Content
{
return new Content(
markdown: 'mail.orders.shipped',
with: [
'url' => $this->orderUrl,
],
);
}
编写 Markdown 消息
Markdown 邮件类使用 Blade 组件和 Markdown 语法的组合,使您能够轻松构建邮件消息,同时利用 Laravel 的预构建电子邮件 UI 组件:
<x-mail::message>
# 订单已发货
您的订单已发货!
<x-mail::button :url="$url">
查看订单
</x-mail::button>
谢谢,<br>
{{ config('app.name') }}
</x-mail::message>
NOTE
编写 Markdown 电子邮件时请勿使用过多缩进。根据 Markdown 标准,Markdown 解析器会将缩进内容渲染为代码块。
按钮组件
按钮组件渲染一个居中的按钮链接。该组件接受两个参数,一个 url
和一个可选的 color
。支持的颜色有 primary
、success
和 error
。您可以在消息中添加任意数量的按钮组件:
<x-mail::button :url="$url" color="success">
查看订单
</x-mail::button>
面板组件
面板组件将给定的文本块渲染在一个背景颜色与消息其余部分略有不同的面板中。这使您可以将注意力集中在给定的文本块上:
<x-mail::panel>
这是面板内容。
</x-mail::panel>
表格组件
表格组件允许您将 Markdown 表格转换为 HTML 表格。该组件接受 Markdown 表格作为其内容。表格列对齐支持使用默认的 Markdown 表格对齐语法:
<x-mail::table>
| Laravel | 表格 | 示例 |
| ------------- | :-----------: | ------------: |
| 列 2 是 | 居中 | $10 |
| 列 3 是 | 右对齐 | $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 组件构建一个全新的主题,可以将 CSS 文件放置在 html/themes
目录中。命名并保存 CSS 文件后,更新应用程序的 config/mail.php
配置文件中的 theme
选项以匹配新主题的名称。
要为单个邮件类自定义主题,可以将邮件类的 $theme
属性设置为发送该邮件类时应使用的主题名称。
发送邮件
要发送消息,请在 Mail
facade 上使用 to
方法。to
方法接受电子邮件地址、用户实例或用户集合。如果您传递一个对象或对象集合,邮件程序将自动使用它们的 email
和 name
属性来确定电子邮件的收件人,因此请确保这些属性在您的对象上可用。指定收件人后,您可以将邮件类实例传递给 send
方法:
<?php
namespace App\Http\Controllers;
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
{
/**
* 发货给定订单。
*/
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);
// 发货订单...
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
方法将电子邮件地址附加到邮件类的收件人列表中,因此循环中的每次迭代都会向每个先前的收件人发送另一封电子邮件。因此,您应该始终为每个收件人重新创建邮件类实例:
foreach (['taylor@example.com', 'dries@example.com'] as $recipient) {
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;
/**
* 创建新消息实例。
*/
public function __construct()
{
$this->afterCommit();
}
}
NOTE
要了解有关解决这些问题的更多信息,请查看有关 队列作业和数据库事务 的文档。
队列邮件失败
当队列邮件发送失败时,如果在队列可发送类(mailable class)中定义了 failed
方法,该方法会被调用。导致队列邮件失败的 Throwable
实例会被传递给 failed
方法:
<?php
namespace App\Mail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Throwable;
class OrderDelayed extends Mailable implements ShouldQueue
{
use SerializesModels;
/**
* 处理队列邮件失败的情况。
*/
public function failed(Throwable $exception): void
{
// ...
}
}
渲染邮件类
有时您可能希望捕获邮件类的 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
{
/**
* 获取用户的首选语言环境。
*/
public function preferredLocale(): string
{
return $this->locale;
}
}
一旦实现了接口,Laravel 将在向模型发送邮件类和通知时自动使用首选语言环境。因此,在使用此接口时无需调用 locale
方法:
Mail::to($request->user())->send(new OrderShipped($order));
测试
测试邮件类内容
Laravel 提供了多种方法用于检查可邮寄对象(mailable)的结构。此外,Laravel 还提供了若干便捷方法,用于测试您的可邮寄对象是否包含您期望的内容:
use App\Mail\InvoicePaid;
use App\Models\User;
test('mailable content', function () {
$user = User::factory()->create();
$mailable = new InvoicePaid($user);
$mailable->assertFrom('jeffrey@example.com');
$mailable->assertTo('taylor@example.com');
$mailable->assertHasCc('abigail@example.com');
$mailable->assertHasBcc('victoria@example.com');
$mailable->assertHasReplyTo('tyler@example.com');
$mailable->assertHasSubject('Invoice Paid');
$mailable->assertHasTag('example-tag');
$mailable->assertHasMetadata('key', 'value');
$mailable->assertSeeInHtml($user->email);
$mailable->assertDontSeeInHtml('Invoice Not Paid');
$mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);
$mailable->assertSeeInText($user->email);
$mailable->assertDontSeeInText('Invoice Not Paid');
$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->assertFrom('jeffrey@example.com');
$mailable->assertTo('taylor@example.com');
$mailable->assertHasCc('abigail@example.com');
$mailable->assertHasBcc('victoria@example.com');
$mailable->assertHasReplyTo('tyler@example.com');
$mailable->assertHasSubject('Invoice Paid');
$mailable->assertHasTag('example-tag');
$mailable->assertHasMetadata('key', 'value');
$mailable->assertSeeInHtml($user->email);
$mailable->assertDontSeeInHtml('Invoice Not Paid');
$mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);
$mailable->assertSeeInText($user->email);
$mailable->assertDontSeeInText('Invoice Not Paid');
$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']);
}
正如你所预期的,“HTML” 断言用于断言可邮寄对象的 HTML 版本包含指定的字符串,而 “text” 断言则用于断言可邮寄对象的纯文本版本包含指定的字符串。
测试邮件类发送
我们建议将邮件类内容的测试与断言给定邮件类已“发送”给特定用户的测试分开。通常,邮件类的内容与您正在测试的代码无关,仅断言 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();
// 执行订单发货...
// 断言没有邮件类被发送...
Mail::assertNothingSent();
// 断言邮件类被发送...
Mail::assertSent(OrderShipped::class);
// 断言邮件类被发送两次...
Mail::assertSent(OrderShipped::class, 2);
// 断言邮件类被发送到电子邮件地址...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// 断言邮件类被发送到多个电子邮件地址...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// 断言邮件类未被发送...
Mail::assertNotSent(AnotherMailable::class);
// 断言某个邮件类被发送了两次...
Mail::assertSentTimes(OrderShipped::class, 2);
// 断言总共发送了 3 个邮件类...
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();
// 执行订单发货...
// 断言没有邮件类被发送...
Mail::assertNothingSent();
// 断言邮件类被发送...
Mail::assertSent(OrderShipped::class);
// 断言邮件类被发送两次...
Mail::assertSent(OrderShipped::class, 2);
// 断言邮件类被发送到电子邮件地址...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// 断言邮件类被发送到多个电子邮件地址...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// 断言邮件类未被发送...
Mail::assertNotSent(AnotherMailable::class);
// 断言某个邮件类被发送了两次...
Mail::assertSentTimes(OrderShipped::class, 2);
// 断言总共发送了 3 个邮件类...
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('...') &&
$mail->usesMailer('ses');
});
邮件类实例还包括几个用于检查邮件类附件的有用方法:
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 运行时,您可以在 http://localhost:8025
访问 Mailpit 界面。
使用全局 to
地址
最后,您可以通过调用 Mail
facade 提供的 alwaysTo
方法来指定全局“to”地址。通常,此方法应在应用程序的服务提供者之一的 boot
方法中调用:
use Illuminate\Support\Facades\Mail;
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
if ($this->app->environment('local')) {
Mail::alwaysTo('taylor@example.com');
}
}
事件
Laravel 在发送邮件消息时会调度两个事件。MessageSending
事件在消息发送之前调度,而 MessageSent
事件在消息发送后调度。请记住,这些事件是在邮件 发送 时调度的,而不是在邮件排队时。您可以在应用程序中为这些事件创建 事件监听器:
use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;
class LogMessage
{
/**
* 处理事件。
*/
public function handle(MessageSending $event): void
{
// ...
}
}
自定义传输
Laravel 包含多种邮件传输方式;然而,您可能希望编写自己的传输方式,通过 Laravel 默认不支持的其他服务发送邮件。要开始,请定义一个继承自 Symfony\Component\Mailer\Transport\AbstractTransport
类的类。然后,在您的传输类中实现 doSend
和 __toString
方法:
<?php
namespace App\Mail;
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
{
/**
* 创建新的 Mailchimp 传输实例。
*/
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(),
]]);
}
/**
* 获取传输的字符串表示。
*/
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;
use MailchimpTransactional\ApiClient;
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Mail::extend('mailchimp', function (array $config = []) {
$client = new ApiClient;
$client->setApiKey($config['key']);
return new MailchimpTransport($client);
});
}
定义并注册自定义传输后,您可以在应用程序的 config/mail.php
配置文件中创建一个使用新传输的邮件程序定义:
'mailchimp' => [
'transport' => 'mailchimp',
'key' => env('MAILCHIMP_API_KEY'),
// ...
],
额外的 Symfony 传输
Laravel 包含对一些现有的 Symfony 维护的邮件传输的支持,如 Mailgun 和 Postmark。然而,您可能希望通过支持额外的 Symfony 维护的传输来扩展 Laravel。您可以通过 Composer 安装必要的 Symfony 邮件程序并将传输注册到 Laravel。例如,您可以安装和注册“Brevo”(以前称为“Sendinblue”)Symfony 邮件程序:
composer require symfony/brevo-mailer symfony/http-client
安装 Brevo 邮件程序包后,您可以将 Brevo API 凭据的条目添加到应用程序的 services
配置文件中:
'brevo' => [
'key' => env('BREVO_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;
/**
* 启动任何应用程序服务。
*/
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',
// ...
],