跳至内容

邮件

简介

发送电子邮件不必复杂。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 传输

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 驱动程序,您必须首先安装适用于 PHP 的 Amazon AWS SDK。您可以通过 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 模板

配置发件人

使用 Envelope

首先,让我们探索配置电子邮件的发件人。或者,换句话说,电子邮件将“来自”谁。配置发件人有两种方法。首先,您可以在邮件的信封上指定“来自”地址。

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 地址

但是,如果您的应用程序对所有电子邮件使用相同的“来自”地址,则将其添加到您生成的每个可邮件类中可能会变得很麻烦。您可以改为在 config/mail.php 配置文件中指定一个全局“来自”地址。如果在可邮件类中未指定其他“来自”地址,则将使用此地址。

'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`。支持的颜色为 `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 组件构建一个全新的主题,您可以将 CSS 文件放在 `html/themes` 目录中。命名并保存 CSS 文件后,更新应用程序的 `config/mail.php` 配置文件中的 `theme` 选项以匹配新主题的名称。

要自定义单个可邮件消息的主题,您可以将可邮件类的 `$theme` 属性设置为发送该可邮件消息时应使用的主题的名称。

发送邮件

要发送邮件,请使用 `Mail` 外观 上的 `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');
}
}

发送邮件时,您不仅限于指定“收件人”。您可以通过将各自的方法链接在一起,自由设置“收件人”、“抄送”和“密送”收件人。

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` 外观上的 `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();
}
}
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` 外观提供了一个 `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 版本包含给定的字符串,而“文本”断言断言可邮件消息的纯文本版本包含给定的字符串。

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` 外观的 `fake` 方法来防止发送邮件。调用 `Mail` 外观的 `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);

您可以将闭包传递给 `assertSent`、`assertNotSent`、`assertQueued` 或 `assertNotQueued` 方法,以断言已发送通过给定“真值测试”的可邮件消息。如果至少发送了一个通过给定真值测试的可邮件消息,则断言将成功。

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

调用 `Mail` 外观的断言方法时,提供的闭包接受的可邮件实例公开了用于检查可邮件消息的有用方法。

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

或者,您可以使用 HELOMailtrap 等服务以及 smtp 驱动程序将您的邮件消息发送到“虚拟”邮箱,您可以在真实的邮件客户端中查看它们。这种方法的好处是允许您实际检查 Mailtrap 的消息查看器中的最终邮件。

如果您使用的是 Laravel Sail,则可以使用 Mailpit 预览您的邮件。当 Sail 运行时,您可以通过以下地址访问 Mailpit 界面:https://127.0.0.1:8025

使用全局 to 地址

最后,您可以通过调用 Mail 门面提供的 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';
}
}

定义并注册自定义传输方式后,您可以在应用程序的 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(/* ... */);
});
}

Mail 门面提供的 extend 方法允许您注册它。通常,这应该在应用程序的 AppServiceProvider 服务提供程序的 boot 方法中完成。一个 $config 参数将传递给提供给 extend 方法的闭包。此参数将包含在应用程序的 config/mail.php 配置文件中为邮件发送程序定义的配置数组。

'mailchimp' => [
'transport' => 'mailchimp',
// ...
],

其他 Symfony 传输

Laravel 支持一些现有的 Symfony 维护的邮件传输方式,例如 Mailgun 和 Postmark。但是,您可能希望扩展 Laravel 以支持其他 Symfony 维护的传输方式。您可以通过 Composer 引用必要的 Symfony mailer 并将其与 Laravel 注册来实现此目的。例如,您可以安装和注册“Brevo”(以前称为“Sendinblue”)Symfony mailer。

composer require symfony/brevo-mailer symfony/http-client

安装 Brevo mailer 包后,您可以为您的 Brevo API 凭据添加到应用程序的 services 配置文件。

'brevo' => [
'key' => 'your-api-key',
],

接下来,您可以使用 Mail 门面的 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',
// ...
],