laravel
5. 深入探讨
邮件

介绍

发送电子邮件不必复杂。Laravel 提供了一个简洁的电子邮件 API,由流行的 Symfony Mailer (opens in a new tab) 组件提供支持。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 区域 (opens in a new tab),您可以在 services 配置文件中定义您所在区域的端点:

'mailgun' => [
    'domain' => env('MAILGUN_DOMAIN'),
    'secret' => env('MAILGUN_SECRET'),
    'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),
    'scheme' => 'https',
],

Postmark 驱动程序

要使用 Postmark (opens in a new tab) 驱动程序,请通过 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 (opens in a new tab) 驱动程序,请通过 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 临时凭证 (opens in a new tab),您可以在应用程序的 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 的 订阅管理功能 (opens in a new tab)进行交互,您可以在邮件消息的 headers 方法返回的数组中返回 X-Ses-List-Management-Options 标头:

/**
 * 获取消息标头。
 */
public function headers(): Headers
{
    return new Headers(
        text: [
            'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic',
        ],
    );
}

如果您想定义 其他选项 (opens in a new tab),以便 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 (opens in a new tab),一个事务性电子邮件和短信服务,为 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 驱动程序文档 (opens in a new tab)

故障转移配置

有时,您为应用程序配置的用于发送邮件的外部服务可能会出现故障。在这些情况下,定义一个或多个备份邮件传递配置会很有用,以便在主要传递驱动程序出现故障时使用。

要实现此目的,您应该在应用程序的 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'),

轮询传输从配置的邮件发送器列表中随机选择一个邮件发送器,然后为每个后续电子邮件切换到下一个可用的邮件发送器。与有助于实现 高可用性 (opens in a new tab)failover 传输不同,roundrobin 传输提供 负载均衡 (opens in a new tab)

生成可邮寄对象(Mailables)

在构建 Laravel 应用程序时,您的应用程序发送的每种类型的电子邮件都表示为一个“可邮寄对象(mailable)”类。这些类存储在 app/Mail 目录中。如果您在应用程序中没有看到此目录,不用担心,因为当您使用 make:mail Artisan 命令创建第一个可邮寄对象类时,它将为您生成:

php artisan make:mail OrderShipped

编写可邮寄对象(Mailables)

一旦生成了可邮寄对象类,打开它以便我们可以探索其内容。可邮寄对象类的配置在几个方法中完成,包括 envelopecontentattachments 方法。

envelope 方法返回一个 Illuminate\Mail\Mailables\Envelope 对象,该对象定义了邮件的主题,有时还定义了收件人。content 方法返回一个 Illuminate\Mail\Mailables\Content 对象,该对象定义了将用于生成邮件内容的 Blade 模板

配置发件人

使用信封

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

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: '订单已发货',
    );
}

如果您愿意,您还可以指定一个“replyTo”地址:

return new Envelope(
    from: new Address('jeffrey@example.com', 'Jeffrey Way'),
    replyTo: [
        new Address('taylor@example.com', 'Taylor Otwell'),
    ],
    subject: '订单已发货',
);

使用全局“from”地址

但是,如果您的应用程序对其所有电子邮件都使用相同的“from”地址,那么在您生成的每个可邮寄对象类中添加它可能会很麻烦。相反,您可以在 config/mail.php 配置文件中指定一个全局的“from”地址。如果在可邮寄对象类中未指定其他“from”地址,则将使用此地址:

'from' => [
    'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
    'name' => env('MAIL_FROM_NAME', '示例'),
],

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

'reply_to' => ['address' => 'example@example.com', 'name' => '应用名称'],

配置视图

在可邮寄对象类的 content 方法中,您可以定义 view,即渲染电子邮件内容时应使用的模板。由于每个电子邮件通常使用 Blade 模板 来渲染其内容,因此在构建电子邮件的 HTML 时,您可以充分利用 Blade 模板引擎的强大功能和便利性:

/**
 * 获取消息内容定义。
 */
public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
    );
}

[!注意]
您可能希望创建一个 resources/views/emails 目录来存放所有电子邮件模板;但是,您可以将它们自由地放置在 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'

附件

若要为电子邮件添加附件,您需要将附件添加到邮件的attachments方法返回的数组中。首先,您可以通过Attachment类提供的fromPath方法并提供文件路径来添加附件:

use Illuminate\Mail\Mailables\Attachment;
 
/**
 * 获取消息的附件。
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromPath('/path/to/file'),
    ];
}

当为消息添加附件时,您还可以使用aswithMime方法为附件指定显示名称和/或 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接口。此接口规定您的类定义一个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
{
    /**
     * 获取模型的可附加表示。
     */
    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 还提供了其他方法,您可以使用它们来自定义您的附件。例如,您可以使用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;
 
/**
 * 获取消息标头。
 */
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 的文档以获取有关标签 (opens in a new tab)元数据 (opens in a new tab)的更多信息。同样,Postmark 文档也可以查阅以获取有关其对标签 (opens in a new tab)元数据 (opens in a new tab)支持的更多信息。

如果您的应用程序使用 Amazon SES 发送电子邮件,则应使用metadata方法将SES“标签” (opens in a new tab)附加到消息中。

自定义 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。支持的颜色为primarysuccesserror。您可以根据需要向消息中添加任意数量的按钮组件:

<x-mail::button :url="$url" color="success">
查看订单
</x-mail::button>

面板组件

面板组件将给定的文本块呈现在一个面板中,该面板的背景颜色与消息的其余部分略有不同。这使您可以将注意力吸引到给定的文本块上:

<x-mail::panel>
这是面板内容。
</x-mail::panel>

表格组件

表格组件允许您将 Markdown 表格转换为 HTML 表格。该组件将 Markdown 表格作为其内容。使用默认的 Markdown 表格对齐语法支持表格列对齐:

<x-mail::table>
| Laravel       | 表格         | 示例       |
| ------------- | :-----------: | ------------: |
| Col 2 is      | 居中        | $10           |
| Col 3 is      | 右对齐      | $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外观上使用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
{
    /**
     * 发货给定的订单。
     */
    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外观上使用queue方法:

Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->queue(new OrderShipped($order));

此方法

排队可邮寄对象与数据库事务

当在数据库事务中调度排队可邮寄对象时,它们可能会在数据库事务提交之前由队列进行处理。当这种情况发生时,您在数据库事务期间对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,在事务中创建的任何模型或数据库记录可能在数据库中不存在。如果您的可邮寄对象依赖于这些模型,那么在处理发送排队可邮寄对象的作业时,可能会发生意外错误。

如果您的队列连接的 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]
要了解更多关于解决这些问题的信息,请查看关于 排队作业和数据库事务 的文档。

渲染可邮寄对象

有时您可能希望在不发送可邮寄对象的情况下捕获其 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
{
    /**
     * 获取用户的首选区域设置。
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}

一旦您实现了该接口,Laravel 在向模型发送可邮寄对象和通知时将自动使用首选区域设置。因此,在使用此接口时无需调用 locale 方法:

Mail::to($request->user())->send(new OrderShipped($order));

测试

测试可邮寄对象内容

Laravel 提供了多种方法来检查您的可邮寄对象的结构。此外,Laravel 还提供了几种方便的方法来测试您的可邮寄对象是否包含您期望的内容。这些方法是:assertSeeInHtmlassertDontSeeInHtmlassertSeeInOrderInHtmlassertSeeInTextassertDontSeeInTextassertSeeInOrderInTextassertHasAttachmentassertHasAttachedDataassertHasAttachmentFromStorageassertHasAttachmentFromStorageDisk

正如您可能期望的那样,“HTML”断言断言您的可邮寄对象的 HTML 版本包含给定的字符串,而“文本”断言断言您的可邮寄对象的纯文本版本包含给定的字符串:

use App\Mail\InvoicePaid;
use App\Models\User;
 
test('可邮寄对象内容', 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->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('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->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('订单可以发货', 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);
 
    // 断言总共发送了 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);
 
        // 断言总共发送了 3 个可邮寄对象...
        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 外观的断言方法时,由提供的闭包接受的可邮寄对象实例公开了用于检查可邮寄对象的有用方法:

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

或者,您可以使用像 HELO (opens in a new tab)Mailtrap (opens in a new tab) 这样的服务以及 smtp 驱动程序将您的电子邮件消息发送到一个“虚拟”邮箱,您可以在真正的电子邮件客户端中查看它们。这种方法的好处是允许您在 Mailtrap 的消息查看器中实际检查最终的电子邮件。

如果您正在使用 Laravel Sail,您可以使用 Mailpit (opens in a new tab) 预览您的消息。当 Sail 运行时,您可以在以下地址访问 Mailpit 界面:http://localhost:8025

使用全局 to 地址

最后,您可以通过调用 Mail 外观提供的 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() 方法:

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
{
    /**