跳至内容

邮件

简介

发送电子邮件并不一定很复杂。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=mailersend
MAIL_FROM_ADDRESS=[email protected]
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',
],
],
 
// ...
],

定义完故障转移邮件发送器后,你应将其设置为应用程序使用的默认邮件发送器,方法是将其名称指定为应用程序的 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

编写邮件

生成邮件类后,打开它,以便我们可以探索其内容。邮件类配置在几个方法中完成,包括 envelopecontentattachments 方法。

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(
from: new Address('[email protected]', 'Jeffrey Way'),
subject: 'Order Shipped',
);
}

如果你愿意,你也可以指定一个 replyTo 地址

return new Envelope(
from: new Address('[email protected]', 'Jeffrey Way'),
replyTo: [
new Address('[email protected]', 'Taylor Otwell'),
],
subject: 'Order Shipped',
);

使用全局 from 地址

但是,如果你的应用程序对所有邮件使用相同的 "from" 地址,那么在生成的每个邮件类中添加它就会变得很繁琐。相反,你可以在 config/mail.php 配置文件中指定一个全局的 "from" 地址。如果在邮件类中没有指定其他 "from" 地址,则会使用此地址

'from' => [
'address' => env('MAIL_FROM_ADDRESS', '[email protected]'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],

此外,你可以在 config/mail.php 配置文件中定义一个全局的 "reply_to" 地址

'reply_to' => ['address' => '[email protected]', 'name' => 'App Name'],

配置视图

在邮件类的 content 方法中,你可以定义 view,也就是在渲染邮件内容时应该使用哪个模板。由于每个邮件通常使用 Blade 模板 来渲染其内容,因此在构建邮件的 HTML 时,你可以充分利用 Blade 模板引擎的强大功能和便利性

/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}
lightbulb

你可能希望创建一个 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 参数手动将数据传递到视图中。通常,你仍然会通过邮件类的构造函数传递数据;但是,你应该将此数据设置为 protectedprivate 属性,这样数据就不会自动提供给模板

<?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'),
];
}

在将文件附加到邮件时,你也可以使用 aswithMime 方法指定附件的显示名称和/或 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>
exclamation

$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 还提供了一些其他方法,你可以使用这些方法来自定义附件。例如,你可以使用 aswithMime 方法来自定义文件的名称和 MIME 类型

return Attachment::fromPath('/path/to/file')
->as('Photo Name')
->withMime('image/jpeg');

标头

有时你可能需要向传出的邮件附加额外的标头。例如,你可能需要设置自定义的 Message-Id 或其他任意文本标头。

要实现这一点,请在你的邮件类上定义一个 headers 方法。headers 方法应该返回一个 Illuminate\Mail\Mailables\Headers 实例。此类接受 messageIdreferencestext 参数。当然,你只需要提供特定邮件所需的参数

use Illuminate\Mail\Mailables\Headers;
 
/**
* Get the message headers.
*/
public function headers(): Headers
{
return new Headers(
messageId: '[email protected]',
references: ['[email protected]'],
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>
lightbulb

在编写 Markdown 邮件时,不要使用过多的缩进。根据 Markdown 标准,Markdown 解析器会将缩进的内容渲染为代码块。

按钮组件

按钮组件渲染一个居中的按钮链接。该组件接受两个参数,一个 url 和一个可选的 color。支持的颜色有 primarysuccesserror。你可以在一条消息中添加任意数量的按钮组件

<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 方法接受电子邮件地址、用户实例或用户集合。如果你传递对象或对象集合,邮件程序会自动使用它们的emailname 属性来确定邮件的收件人,所以请确保这些属性在你的对象中可用。指定完收件人后,你可以将你的可邮件类实例传递给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 方法将电子邮件地址追加到可邮件的收件人列表中,因此每次循环迭代都会向所有之前的收件人发送另一封邮件。因此,你应该始终为每个收件人重新创建可邮件实例

foreach (['[email protected]', '[email protected]'] 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,因此你可以在任何邮件类实例上调用onQueueonConnection 方法,从而允许你为消息指定连接和队列名称

$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();
}
}
lightbulb

要了解有关解决这些问题的更多信息,请查看有关排队的作业和数据库事务 的文档。

渲染邮件

有时你可能希望捕获邮件的 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 还提供了一些方便的方法来测试你的邮件是否包含你期望的内容。这些方法是:assertSeeInHtmlassertDontSeeInHtmlassertSeeInOrderInHtmlassertSeeInTextassertDontSeeInTextassertSeeInOrderInTextassertHasAttachmentassertHasAttachedDataassertHasAttachmentFromStorageassertHasAttachmentFromStorageDisk

正如你可能期望的那样,“HTML” 断言断言邮件的 HTML 版本包含给定的字符串,而“text” 断言断言邮件的纯文本版本包含给定的字符串

use App\Mail\InvoicePaid;
use App\Models\User;
 
test('mailable content', function () {
$user = User::factory()->create();
 
$mailable = new InvoicePaid($user);
 
$mailable->assertFrom('[email protected]');
$mailable->assertTo('[email protected]');
$mailable->assertHasCc('[email protected]');
$mailable->assertHasBcc('[email protected]');
$mailable->assertHasReplyTo('[email protected]');
$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->assertFrom('[email protected]');
$mailable->assertTo('[email protected]');
$mailable->assertHasCc('[email protected]');
$mailable->assertHasBcc('[email protected]');
$mailable->assertHasReplyTo('[email protected]');
$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...
Mail::assertSent(OrderShipped::class, '[email protected]');
 
// Assert a mailable was sent to multiple email addresses...
Mail::assertSent(OrderShipped::class, ['[email protected]', '...']);
 
// 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...
Mail::assertSent(OrderShipped::class, '[email protected]');
 
// Assert a mailable was sent to multiple email addresses...
Mail::assertSent(OrderShipped::class, ['[email protected]', '...']);
 
// 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);

你可以将闭包传递给assertSentassertNotSentassertQueuedassertNotQueued 方法,以断言发送的邮件通过给定的“真值测试”。如果至少有一封邮件发送通过了给定的真值测试,则断言将成功

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')
);
});

你可能已经注意到,有两种方法可以断言邮件没有发送:assertNotSentassertNotQueued。有时你可能希望断言没有发送或排队邮件。要实现这一点,你可以使用assertNothingOutgoingassertNotOutgoing 方法

Mail::assertNothingOutgoing();
 
Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});

邮件和本地开发

在开发发送电子邮件的应用程序时,你可能不想实际将电子邮件发送到实际的电子邮件地址。Laravel 提供了几种方法在本地开发过程中“禁用”实际发送电子邮件。

日志驱动程序

log 邮件驱动程序不会发送电子邮件,而是将所有电子邮件消息写入日志文件以供检查。通常,此驱动程序仅在本地开发期间使用。有关根据环境配置应用程序的更多信息,请查看配置文档.

HELO / Mailtrap / Mailpit

或者,你可以使用HELOMailtrap 等服务以及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')) {
Mail::alwaysTo('[email protected]');
}
}

事件

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',
// ...
],