发送电子邮件不必复杂。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,通常比通过 SMTP 服务器发送邮件更简单、更快。只要有可能,我们建议您使用其中一个驱动程序。
要使用 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 驱动程序,请通过 Composer 安装 Symfony 的 Postmark Mailer 传输:
composer require symfony/postmark-mailer symfony/http-client接下来,将您的应用程序的 config/mail.php 配置文件中的 default 选项设置为 postmark。配置完您的应用程序的默认邮件发送器后,请确保您的 config/services.php 配置文件包含以下选项:
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],如果您想指定应由给定邮件程序使用的 Postmark 消息流,您可以将 message_stream_id 配置选项添加到邮件程序的配置数组中。此配置数组可以在您的应用程序的 config/mail.php 配置文件中找到:
'postmark' => [
'transport' => 'postmark',
'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],通过这种方式你也可以设置多个使用不同消息流的 Postmark 邮件发送器。
要使用 Resend 驱动程序,请通过 Composer 安装 Resend 的 PHP SDK:
composer require resend/resend-php接下来,在你的应用程序的config/mail.php配置文件中,将default选项设置为resend。在配置好你的应用程序的默认邮件发送器后,确保你的config/services.php配置文件包含以下选项:
'resend' => [
'key' => env('RESEND_API_KEY'),
],要使用 Amazon SES 驱动程序,您必须首先安装适用于 PHP 的 Amazon AWS SDK。您可以通过 Composer 包管理器安装此库:
composer require aws/aws-sdk-php接下来, 将 default 选项在你 config/mail.php 配置文件中设置为 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 临时凭证通过会话令牌,您可以添加一个 token 键到您的应用程序的 SES 配置中:
'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'],
],
],
],有时,您已配置的用于发送您应用程序的邮件的外部服务可能已宕机。在这种情况下,定义一个或多个备用邮件投递配置会很有用,以便在您的主投递驱动程序宕机时使用。
为实现此目的,你应该在你的应用程序的 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'),轮询传输从已配置的邮件发送器列表中随机选择一个邮件发送器,然后为每封后续邮件切换到下一个可用的邮件发送器。与 故障转移 传输(它有助于实现 高可用性)不同的是,轮询 传输提供 负载均衡.
构建 Laravel 应用程序时,应用程序发送的每种电子邮件都表示为一个“可邮寄”类。这些类存储在 app/Mail 目录中。如果您的应用程序中没有看到此目录,请不要担心,因为它将在您使用 make:mail Artisan 命令创建第一个可邮寄类时为您生成:
php artisan make:mail OrderShipped一旦你生成了一个 mailable 类,就打开它,以便我们可以探究其内容。mailable 类配置是在几个方法中完成的,包括 envelope、content 和 attachments 方法。
envelope 方法返回一个 Illuminate\Mail\Mailables\Envelope 对象,该对象定义了邮件主题和,有时,邮件的收件人。 content 方法返回一个 Illuminate\Mail\Mailables\Content 对象,它定义了将用于生成邮件内容的 Blade 模板。
首先,让我们探讨如何配置电子邮件的发件人。换句话说,即电子邮件将“来自”谁。有两种方式配置发件人。首先,您可以指定在您的邮件信封上的“发件人”地址:
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
/**
* Get the message 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',
],在邮件类 (mailable class) 的 content 方法中,你可以定义 view,即渲染邮件内容时应使用哪个模板。由于每封邮件通常会使用 Blade 模板 来渲染其内容,因此在构建邮件的 HTML 时,你拥有 Blade 模板引擎的全部功能和便利:
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}[!NOTE]
你可能希望创建一个resources/views/mail目录来存放你所有的电子邮件模板; 然而, 你可以自由地将它们放置在你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 时使用。有两种方法可以使数据对您的视图可用。首先,在您的 mailable 类上定义的任何公共属性都将自动对视图可用。因此,例如,您可以将数据传递到您的 mailable 类的构造函数中,并将该数据设置为在该类上定义的公共属性:
<?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 参数手动将数据传递给视图。 通常,您仍然会通过 mailable 类的构造函数传递数据; 但是,您应该将这些数据设置为 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>[!WARNING]
$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(
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;
/**
* 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“标签” 到消息中。
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 编写的,Laravel 能够为这些消息渲染出美观、响应式的 HTML 模板,同时还能自动生成纯文本版本。
要生成带有相应 Markdown 模板的可邮寄对象,你可以使用 --markdown 选项,该选项属于 make:mail Artisan 命令:
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 邮件可结合使用 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>[!NOTE]
在编写 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 目录,每个目录都包含所有可用组件的相应表示。您可以随意自定义这些组件。
导出组件后,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\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 (['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 门面上的 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();
}
}[!注意]
要了解有关如何解决这些问题的更多信息,请查阅有关队列作业和数据库事务的文档。
当一个排队的电子邮件失败时,如果队列邮件类上定义了 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;
/**
* Handle a queued email's failure.
*/
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 门面提供了一个 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 提供了多种检查可邮寄对象(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 方法后,您就可以断言 mailable 已被指示发送给用户,甚至检查 mailable 收到的数据:
<?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, 'example@laravel.com');
// Assert a mailable was sent to multiple email addresses...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// Assert a mailable was not sent...
Mail::assertNotSent(AnotherMailable::class);
// Assert a mailable was sent twice...
Mail::assertSentTimes(OrderShipped::class, 2);
// 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, 'example@laravel.com');
// Assert a mailable was sent to multiple email addresses...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// Assert a mailable was not sent...
Mail::assertNotSent(AnotherMailable::class);
// Assert a mailable was sent twice...
Mail::assertSentTimes(OrderShipped::class, 2);
// 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('...') &&
$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 的服务,并使用 smtp 驱动程序将您的电子邮件消息发送到一个 "虚拟" 邮箱,您可以在真实的电子邮件客户端中查看它们。这种方法的好处是允许您在 Mailtrap 的消息查看器中实际检查最终的电子邮件。
如果您正在使用 Laravel Sail,您可以使用 Mailpit 预览您的消息。当 Sail 运行时,您可以通过以下地址访问 Mailpit 界面:http://localhost:8025。
to 地址最后,你可以通过调用 Mail 外观提供的 alwaysTo 方法来指定一个全局的“收件人”地址。通常,这个方法应该在你的某个应用程序服务提供者的 boot 方法中调用:
use Illuminate\Support\Facades\Mail;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
if ($this->app->environment('local')) {
Mail::alwaysTo('taylor@example.com');
}
}当使用 alwaysTo 方法时,邮件消息中的任何额外“抄送”或“密送”地址都将被移除。
Laravel 在发送邮件消息时会分派两个事件。在邮件消息发送之前,会分派 MessageSending 事件;而在邮件消息发送之后,则会分派 MessageSent 事件。请记住,这些事件是在邮件被 发送 时分派的,而不是在邮件被排队时分派的。你可以在你的应用中为这些事件创建 事件监听器:
use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;
class LogMessage
{
/**
* Handle the event.
*/
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
{
/**
* 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 外观提供的 extend 方法注册它。通常,这应该在你的应用程序的 AppServiceProvider 的 boot 方法中完成。一个 $config 参数将传递给提供给 extend 方法的闭包。此参数将包含在应用程序的 config/mail.php 配置文件中为邮件发送器定义的配置数组:
use App\Mail\MailchimpTransport;
use Illuminate\Support\Facades\Mail;
use MailchimpTransactional\ApiClient;
/**
* Bootstrap any application services.
*/
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'),
// ...
],Laravel 包含对一些现有的 Symfony 维护的邮件传输器的支持, 例如 Mailgun 和 Postmark. However, 您可能希望扩展 Laravel 以支持额外的 Symfony 维护的传输器. 您可以通过通过 Composer 引入必要的 Symfony 邮件器, 并向 Laravel 注册该传输器来做到这一点. For example, 您可以安装并注册 "Brevo" (原名为 "Sendinblue") Symfony 邮件器:
composer require symfony/brevo-mailer symfony/http-client一旦 Brevo 邮件发送器包安装完成,您可以在应用程序的 services 配置文件中为您的 Brevo API 凭证添加一个条目:
'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;
/**
* 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',
// ...
],