邮件


简介

Laravel 基于 SwiftMailer 库提供了一套干净、清爽的邮件 API。Laravel 为 SMTP、Mailgun、Postmark、SparkPost、Amazon SES、以及 sendmail 提供了相应的驱动,从而允许你快速通过本地或云服务发送邮件。

配置

Laravel 的电子邮件服务可以通过应用程序的 config/mail.php 配置文件进行配置。在此文件中配置的每个邮件发送器都可以具有自己独特的配置,甚至可以具有自己独特的"传输方式",从而使您的应用程序可以使用不同的电子邮件服务来发送特定的电子邮件。例如,您的应用程序可以使用 Postmark 发送事务性电子邮件,同时使用Amazon SES 发送批量电子邮件。

在您的邮件配置文件中,您将找到一个 mailers 配置数组。该数组包含了 Laravel 支持的主要邮件驱动程序/传输的示例配置条目,而默认配置值确定了在您的应用程序需要发送电子邮件时默认使用的邮件发送器。

驱动程序/传输前提条件

基于 API 的驱动程序(如 Mailgun、Postmark 和 MailerSend)通常比通过 SMTP 服务器发送邮件更简单快捷。在可能的情况下,我们建议您使用其中之一的驱动程序。

Mailgun 驱动

要使用 Mailgun 驱动程序,请通过 Composer 安装 Symfony 的 Mailgun Mailer 传输方式:

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

然后,在您的应用程序的 config/mail.php 配置文件中设置默认选项为 mailgun。配置完应用程序的默认邮件发送器后,请确保您的 config/services.php 配置文件包含以下选项:

'mailgun' => [
    'transport' => 'mailgun',
    'domain' => env('MAILGUN_DOMAIN'),
    'secret' => env('MAILGUN_SECRET'),
],

如果你不是使用「美国」区域的 Mailgun,可以在 services 配置文件中定义区域端点:

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

Postmark 驱动

要使用 Postmark 驱动,需要先通过 Composer 安装 Symfony 的 Postmark SwiftMailer 传输包:

composer require wildbit/swiftmailer-postmark

接下来,在您的应用程序的 config/mail.php 配置文件中将默认选项设置为 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'),
],

通过这种方式,您还可以设置具有不同消息流的多个 Postmark 发送器。

SES 驱动程序

要使用 Amazon SES 驱动程序,您必须首先安装 Amazon AWS SDK for PHP。您可以通过 Composer 包管理器安装此库:

composer require aws/aws-sdk-php

接下来,在 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 临时凭据,可以在应用程序的 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'),
],

如果您想定义 Laravel 传递给 AWS SDK 的 SendEmail 方法在发送电子邮件时应传递的其他选项,可以在 ses 配置中定义一个 options 数组:

'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'options' => [
        'ConfigurationSetName' => 'MyConfigurationSet',
        'EmailTags' => [
            ['Name' => 'foo', 'Value' => 'bar'],
        ],
    ],
],

MailerSend 驱动程序

MailerSend 是一个事务性电子邮件和短信服务,为 Laravel 维护了基于 API 的邮件驱动程序。包含驱动程序的软件包可以通过 Composer 包管理器进行安装:

composer require mailersend/laravel-driver

安装软件包后,将 MAILERSEND_API_KEY 环境变量添加到应用程序的 .env 文件中。此外,应定义MAIL_MAILER 环境变量为 mailersend

MAIL_MAILER=mailersend
MAIL_FROM_ADDRESS=app@yourdomain.com
MAIL_FROM_NAME="App Name"

MAILERSEND_API_KEY=your-api-key

要了解有关 MailerSend 的更多信息,包括如何使用托管模板,请参阅 MailerSend 驱动程序文档

故障转移配置

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

为了实现这一点,应该在应用程序的邮件配置文件中定义一个使用故障转移传输的邮件发送器。应用程序的故障转移邮件发送器的配置数组应包含一个邮件发送器的数组,其中引用了邮件驱动程序应按顺序选择用于传递的顺序:

'mailers' => [
    'failover' => [
        'transport' => 'failover',
        'mailers' => [
            'postmark',
            'mailgun',
            'sendmail',
        ],
    ],
 
    // ...
],

一旦定义了故障转移邮件发送器,应该通过在应用程序的邮件配置文件中将其名称作为默认配置密钥的值来将该邮件发送器设置为应用程序使用的默认邮件发送器:

'default' => env('MAIL_MAILER', 'failover'),

生成可发送邮件

构建 Laravel 应用的过程中,每种类型的电子邮件都表示为一个"mailable"类。这些类存储在 app/Mail 目录中。如果你在应用程序中找不到这个目录,不用担心,因为当你使用 make:mail Artisan命令创建第一个 mailable 类时,它将会自动生成该目录:

php artisan make:mail OrderShipped

编写可发送邮件

编写 mailable 类时,需要在多个方法中进行配置,包括 envelopecontentattachments 方法。

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

配置发件人

使用 envelope

首先,让我们来探讨如何配置电子邮件的发件人,或者换句话说,电子邮件将要"来自"谁。有两种方法可以配置发件人。首先,在邮件的信封中指定"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: '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 地址

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

'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 类的 content 方法中,您可以定义视图,也就是渲染电子邮件内容时将使用的模板。由于每个电子邮件通常使用一个 Blade 模板来渲染其内容,所以在构建电子邮件的 HTML 内容时,您可以充分利用 Blade 模板引擎的强大和便捷:

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

您可以在 resources/views 目录下创建一个 resources/views/emails 目录,用于存放所有的电子邮件模板;当然,您也可以自由地将它们放在 resources/views 目录的任何地方。

纯文本电子邮件

如果您希望定义电子邮件的纯文本版本,可以在创建消息的 Content 定义时指定纯文本模板。像 view 参数一样,text 参数应该是一个模板名称,用于渲染电子邮件的内容。您可以自由地定义消息的 HTML 和纯文本版本:

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

为了清晰起见,html 参数可以作为 view 参数的别名使用:

return new Content(
    html: 'emails.orders.shipped',
    text: 'emails.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;
 
    /**
     * 创建一个新的消息实例。
     */
    public function __construct(
        public Order $order,
    ) {}
 
    /**
     * 获取消息内容定义。
     */
    public function content(): Content
    {
        return new Content(
            view: 'emails.orders.shipped',
        );
    }
}

一旦数据被设置为公共属性,它将自动在视图中可用,所以您可以像访问 Blade 模板中的其他数据一样访问它:

<div>
    价格:{{ $order->price }}
</div>

通过 with 参数

如果您希望在将数据传递给模板之前自定义电子邮件数据的格式,可以通过 Content 定义的 with 参数将数据手动传递给视图。通常情况下,您仍然会通过 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;
 
    /**
     * 创建一个新的消息实例。
     */
    public function __construct(
        protected Order $order,
    ) {}
 
    /**
     * 获取消息内容定义。
     */
    public function content(): Content
    {
        return new Content(
            view: 'emails.orders.shipped',
            with: [
                'orderName' => $this->order->name,
                'orderPrice' => $this->order->price,
            ],
        );
    }
}

一旦数据传递给了with方法,它将自动在视图中可用,所以您可以像访问 Blade 模板中的其他数据一样访问它:

<div>
    价格:{{ $orderPrice }}
</div>

附件

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

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

当附加文件到消息时,您还可以使用 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 方法。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>

在纯文本消息模板中,$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
{
    /**
     * Get the attachable representation of the model.
     */
    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('照片名称')
        ->withMime('image/jpeg');

标题

有时,您可能需要向即将发送的消息附加额外的标题。例如,您可能需要设置自定义的 Message-Id 或其他任意文本标题。

为了实现这一点,在您的 mailable 类上定义一个 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 的文档,了解有关标签元数据的更多信息。同样,可以参考 Postmark 的文档,了解有关其对标签元数据支持的更多信息。

如果您的应用程序使用 Amazon SES 发送电子邮件,您应该使用 metadata 方法向消息添加SES "标签"

自定义 Symfony Message

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 mailable 消息允许您充分利用邮件通知的预先构建的模板和组件。由于这些消息是用 Markdown 编写的,所以 Laravel 能够渲染出漂亮、响应式的 HTML 模板,并自动生成纯文本版本。

生成 Markdown Mailables

要生成一个带有对应 Markdown 模板的 mailable,可以使用 make:mail Artisan 命令的 --markdown 选项:

php artisan make:mail OrderShipped --markdown=emails.orders.shipped

然后,在 mailable 类的 content 方法中配置 mailable Content 定义时,使用 markdown 参数代替 view 参数:

use Illuminate\Mail\Mailables\Content;
 
/**
 * 获取消息的内容定义。
 */
public function content(): Content
{
    return new Content(
        markdown: 'emails.orders.shipped',
        with: [
            'url' => $this->orderUrl,
        ],
    );
}

编写 Markdown 消息

Markdown mailables 使用 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>

在编写 Markdown 电子邮件时,请不要使用多余的缩进。根据 Markdown 标准,Markdown 解析器将缩进内容视为代码块。

Button 组件

button 组件呈现一个居中的按钮链接。该组件接受两个参数,一个 url 和一个可选的颜色。支持的颜色有primarysuccesserror。您可以在消息中添加任意数量的 button 组件:

<x-mail::button :url="$url" color="success">
View Order
</x-mail::button>

Panel 组件

panel 组件在稍微与消息的其他部分不同的背景色中渲染给定的文本块。这样可以将注意力集中在给定的文本块上:

<x-mail::panel>
This is the panel content.
</x-mail::panel>

Table 组件

table 组件允许您将 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 asset 标签发布到 resources/views/vendor/mail 目录中:

php artisan vendor:publish --tag=laravel-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 选项更新为与新主题的名称相匹配。

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

发送邮件

要发送消息,请使用 Mail 门面的 to 方法。to 方法接受电子邮件地址、用户实例或用户集合作为参数。如果传递的是对象或对象集合,邮件将自动使用它们的电子邮件和姓名属性来确定邮件的收件人,请确保这些属性在您的对象上是可用的。一旦您指定了收件人,您可以将可邮寄的类的实例传递给 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 将使用应用程序邮件配置文件中配置为默认传输方法的邮件传输器发送邮件。但是,您可以使用 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 特性,您可以在任何可邮寄类实例上调用 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;

    /**
     * 创建一个新的消息实例。
     */
    public function __construct()
    {
        $this->afterCommit();
    }

}

有关解决这些问题的更多信息,请参阅有关排队作业和数据库事务的文档

渲染可邮寄内容

有时您可能希望捕获可邮寄的 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);
});

在浏览器中预览时,行内附件不会被渲染。要预览这些可邮件,请将它们发送到电子邮件测试应用程序(如 MailpitHELO)。

本地化邮件

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 版本是否包含给定的字符串,而“text”断言断言可邮寄的纯文本版本是否包含给定的字符串:

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

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::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 提供了几种在本地开发期间“禁用”实际发送电子邮件的方法。

日志驱动程序

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

HELO / Mailtrap / Mailpit

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

如果您使用的是 Laravel Sail,则可以使用 Mailpit 预览邮件。在运行 Sail 时,可以在以下 URL 中访问 Mailpit 界面:http://localhost:8025

使用全局地址

最后,您可以通过在一个应用程序的服务提供者的 boot 方法中调用 Mail 门面的 alwaysTo 方法来指定一个全局的“to”地址:

use Illuminate\Support\Facades\Mail;
 
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    if ($this->app->environment('local')) {
        Mail::alwaysTo('taylor@example.com');
    }
}

事件

Laravel 在发送邮件消息的过程中触发了两个事件。在发送消息之前,会触发 MessageSending 事件;在发送消息之后,会触发 MessageSent 事件。请记住,这些事件是在发送邮件时触发的,而不是在排队邮件时触发的。您可以在 App\Providers\EventServiceProvider 服务提供者中的 listen 属性中为这个事件注册事件监听器:

use App\Listeners\LogSendingMessage;
use App\Listeners\LogSentMessage;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Mail\Events\MessageSent;
 
/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    MessageSending::class => [
        LogSendingMessage::class,
    ],
 
    MessageSent::class => [
        LogSentMessage::class,
    ],
];

自定义传输

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
{
    /**
     * 创建一个新的Mailchimp传输实例。
     */
    public function __construct(
        protected ApiClient $client,
    ) {
        parent::__construct();
    }
    
    /**
     * {@inheritDoc}
     */
    protected function doSend(SentMessage $message): void
    {
        $email = MessageConverter::toEmail($message->getOriginalMessage());

        $this->client->messages->send(['message' => [
            'from_email' => $email->getFrom(),
            'to' => collect($email->getTo())->map(function (Address $email) {
                return ['email' => $email->getAddress(), 'type' => 'to'];
            })->all(),
            'subject' => $email->getSubject(),
            'text' => $email->getTextBody(),
        ]]);
    }

    /**
     * 获取传输的字符串表示形式。
     */
    public function __toString(): string
    {
        return 'mailchimp';
    }
}

一旦您定义了自定义的传输方式,您可以通过 Mail 门面提供的 extend 方法进行注册。通常情况下,这应该在您的应用程序的 AppServiceProvider 服务提供者的 boot 方法中完成。一个 $config 参数将传递给 extend 方法提供的闭包。这个参数将包含在应用程序的 config/mail.php 配置文件中为邮件发送器定义的配置数组:

use App\Mail\MailchimpTransport;
use Illuminate\Support\Facades\Mail;
 
/**
 * 初始化任何应用程序服务
 */
public function boot(): void
{
    Mail::extend('mailchimp', function (array $config = []) {
        return new MailchimpTransport(/* ... */);
    });
}

定义和注册您的自定义传输方式后,您可以在应用程序的 config/mail.php 配置文件中创建一个邮件传输程序定义来使用新的传输方式:

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

额外的 Symfony 传输方式

Laravel支持一些现有的 Symfony 维护的邮件传输方式,如 Mailgun 和 Postmark。但是,您可能希望使用其他由Symfony维护的传输方式扩展Laravel。您可以通过使用 Composer 来安装和注册所需的 Symfony mailer 来完成此操作。例如,您可以安装并注册“Brevo”(曾用名为“Sendinblue”) Symfony mailer:

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

安装 Brevo mailer 软件包后,可以将 Brevo API 凭据的条目添加到应用程序的服务配置文件中:

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

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 本地化

>> 下一篇: 通知