Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

邮件

简介

发送电子邮件不一定很复杂。Laravel 提供了一个干净、简单的邮件 API,它由流行的 Symfony Mailer 组件驱动。Laravel 和 Symfony Mailer 提供了通过 SMTP、Cloudflare、Mailgun、Postmark、Resend、Amazon SES 和 sendmail 发送邮件的驱动程序,使你可以快速开始通过你选择的本地或基于云的服务发送邮件。

配置

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

在你的 mail 配置文件中,你会找到一个 mailers 配置数组。这个数组包含了 Laravel 支持的每个主要邮件驱动/传输的示例配置条目,而 default 配置值决定了当你的应用程序需要发送电子邮件时默认使用哪个邮件程序。

驱动 / 传输前提条件

基于 API 的驱动,如 Mailgun、Postmark 和 Resend,通常比通过 SMTP 服务器发送邮件更简单、更快捷。只要有可能,我们建议你使用这些驱动之一。

Cloudflare 驱动

要使用 Cloudflare 驱动,请通过 Composer 安装 Symfony 的 HTTP 客户端:

shell
composer require symfony/http-client

接下来,你需要在应用程序的 config/mail.php 配置文件中进行两处修改。首先,将默认邮件发送器设置为 cloudflare

php
'default' => env('MAIL_MAILER', 'cloudflare'),

其次,将以下配置数组添加到 mailers 数组中:

php
'cloudflare' => [
    'transport' => 'cloudflare',
],

在配置好应用程序的默认邮件发送器之后,将以下选项添加到 config/services.php 配置文件中:

php
'cloudflare' => [
    'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
    'key' => env('CLOUDFLARE_KEY'),
],

Mailgun 驱动

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

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

接下来,你需要在应用程序的 config/mail.php 配置文件中进行两处更改。首先,将你的默认邮件程序设置为 mailgun

php
'default' => env('MAIL_MAILER', 'mailgun'),

其次,将以下配置数组添加到你的 mailers 数组中:

php
'mailgun' => [
    'transport' => 'mailgun',
    // 'client' => [
    //     'timeout' => 5,
    // ],
],

配置好应用程序的默认邮件程序后,将以下选项添加到你的 config/services.php 配置文件中:

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

如果你没有使用美国 Mailgun 区域,你可以在 services 配置文件中定义你所在区域的端点:

php
'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 传输:

shell
composer require symfony/postmark-mailer symfony/http-client

接下来,将应用程序 config/mail.php 配置文件中的 default 选项设置为 postmark。配置好应用程序的默认邮件程序后,确保你的 config/services.php 配置文件包含以下选项:

php
'postmark' => [
    'key' => env('POSTMARK_API_KEY'),
],

如果你想指定给定邮件程序应使用的 Postmark 消息流,你可以将 message_stream_id 配置选项添加到邮件程序的配置数组中。此配置数组可以在你的应用程序的 config/mail.php 配置文件中找到:

php
'postmark' => [
    'transport' => 'postmark',
    'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
    // 'client' => [
    //     'timeout' => 5,
    // ],
],

这样,你还可以设置多个具有不同消息流的 Postmark 邮件程序。

Resend 驱动

要使用 Resend 驱动,请通过 Composer 安装 Resend 的 PHP SDK:

shell
composer require resend/resend-php

接下来,将应用程序 config/mail.php 配置文件中的 default 选项设置为 resend。配置好应用程序的默认邮件程序后,确保你的 config/services.php 配置文件包含以下选项:

php
'resend' => [
    'key' => env('RESEND_API_KEY'),
],

SES 驱动

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

shell
composer require aws/aws-sdk-php

接下来,将 config/mail.php 配置文件中的 default 选项设置为 ses,并验证你的 config/services.php 配置文件包含以下选项:

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 配置中:

php
'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 头:

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

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

php
'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 数组,该数组引用了配置的邮件程序应被选择用于传递的顺序:

php
'mailers' => [
    'failover' => [
        'transport' => 'failover',
        'mailers' => [
            'postmark',
            'mailgun',
            'sendmail',
        ],
        'retry_after' => 60,
    ],

    // ...
],

一旦你配置了使用 failover 传输的邮件程序,你需要将故障转移邮件程序设置为应用程序 .env 文件中的默认邮件程序,以利用故障转移功能:

ini
MAIL_MAILER=failover

轮询配置

roundrobin 传输允许你将邮件发送工作负载分布到多个邮件程序上。首先,在你的应用程序的 mail 配置文件中定义一个使用 roundrobin 传输的邮件程序。你的应用程序 roundrobin 邮件程序的配置数组应包含一个 mailers 数组,该数组引用了哪些已配置的邮件程序应用于传递:

php
'mailers' => [
    'roundrobin' => [
        'transport' => 'roundrobin',
        'mailers' => [
            'ses',
            'postmark',
        ],
        'retry_after' => 60,
    ],

    // ...
],

定义好轮询邮件程序后,你应通过将其名称指定为应用程序 mail 配置文件中 default 配置键的值,将此邮件程序设置为应用程序使用的默认邮件程序:

php
'default' => env('MAIL_MAILER', 'roundrobin'),

轮询传输从配置的邮件程序列表中随机选择一个邮件程序,然后为每封后续邮件切换到下一个可用的邮件程序。与有助于实现*高可用性failover 传输相比,roundrobin 传输提供了负载均衡*。

生成可邮寄类

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

shell
php artisan make:mail OrderShipped

编写可邮寄类

生成可邮寄类后,打开它以便我们可以探索其内容。可邮寄类的配置通过几种方法完成,包括 envelopecontentattachments 方法。

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

配置发件人

使用信封

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

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

php
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 配置文件中指定一个全局的“发件人”地址。如果在可邮寄类中没有指定其他“发件人”地址,将使用此地址:

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

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

php
'reply_to' => [
    'address' => 'example@example.com',
    'name' => 'App Name',
],

配置视图

在可邮寄类的 content 方法中,你可以定义 view,即用于渲染电子邮件内容的模板。由于每封电子邮件通常使用 Blade 模板来渲染其内容,因此在构建电子邮件的 HTML 时,你可以拥有 Blade 模板引擎的全部功能和便利性:

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

NOTE

你可能希望创建一个 resources/views/mail 目录来存放所有电子邮件模板;但是,你可以自由地将它们放在 resources/views 目录中的任何位置。

纯文本电子邮件

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

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

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

php
return new Content(
    html: 'mail.orders.shipped',
    text: 'mail.orders.shipped-text'
);

视图数据

通过公共属性

通常,你会希望将一些数据传递给视图,以便在渲染电子邮件的 HTML 时使用。有两种方法可以使数据可用于你的视图。首先,你的可邮寄类上定义的任何公共属性将自动可用于视图。因此,例如,你可以将数据传递到可邮寄类的构造函数中,并将这些数据设置为类上定义的公共属性:

php
<?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: 'mail.orders.shipped',
        );
    }
}

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

blade
<div>
    Price: {{ $order->price }}
</div>

通过 with 参数

如果你希望在将电子邮件的数据发送到模板之前自定义其格式,你可以通过 Content 定义的 with 参数手动将数据传递给视图。通常,你仍然通过可邮寄类的构造函数传递数据;但是,你应将此数据设置为 protectedprivate 属性,以便数据不会自动可用于模板:

php
<?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: 'mail.orders.shipped',
            with: [
                'orderName' => $this->order->name,
                'orderPrice' => $this->order->price,
            ],
        );
    }
}

一旦数据通过 with 参数传递,它将自动在你的视图中可用,因此你可以像访问 Blade 模板中的任何其他数据一样访问它:

blade
<div>
    Price: {{ $orderPrice }}
</div>

附件

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

php
use Illuminate\Mail\Mailables\Attachment;

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

向消息附加文件时,你还可以使用 aswithMime 方法指定附件的显示名称和/或 MIME 类型:

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

从磁盘附加文件

如果你已将文件存储在某个文件系统磁盘上,你可以使用 fromStorage 附件方法将其附加到电子邮件:

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

当然,你也可以指定附件的名称和 MIME 类型:

php
/**
 * 获取消息的附件。
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorage('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}

如果需要指定除默认磁盘以外的存储磁盘,可以使用 fromStorageDisk 方法:

php
/**
 * 获取消息的附件。
 *
 * @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 方法接受一个解析原始数据字节的闭包以及附件应分配的名称:

php
/**
 * 获取消息的附件。
 *
 * @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 变量在所有电子邮件模板中可用,因此你无需担心手动传递它:

blade
<body>
    Here is an image:

    <img src="{{ $message->embed($pathToImage) }}">
</body>

WARNING

$message 变量在纯文本消息模板中不可用,因为纯文本消息不使用内联附件。

嵌入原始数据附件

如果你已经有一个希望嵌入到电子邮件模板中的原始图像数据字符串,你可以在 $message 变量上调用 embedData 方法。调用 embedData 方法时,你需要提供应分配给嵌入图像的文件名:

blade
<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
<?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 方法返回该对象的实例:

php
/**
 * 获取消息的附件。
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [$this->photo];
}

当然,附件数据可能存储在远程文件存储服务上,例如 Amazon S3。因此,Laravel 还允许你从存储在应用程序的某个文件系统磁盘上的数据生成附件实例:

php
// 从默认磁盘上的文件创建附件...
return Attachment::fromStorage($this->path);

// 从特定磁盘上的文件创建附件...
return Attachment::fromStorageDisk('backblaze', $this->path);

此外,你可以通过内存中的数据创建附件实例。要实现这一点,向 fromData 方法提供一个闭包。该闭包应返回表示附件的原始数据:

php
return Attachment::fromData(fn () => $this->content, 'Photo Name');

Laravel 还提供了可用于自定义附件的其他方法。例如,你可以使用 aswithMime 方法来自定义文件的名称和 MIME 类型:

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

头信息

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

为此,在你的可邮寄类上定义一个 headers 方法。headers 方法应返回一个 Illuminate\Mail\Mailables\Headers 实例。此类接受 messageIdreferencestext 参数。当然,你只能为你特定消息所需的参数提供值:

php
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 定义向电子邮件消息添加标签和元数据:

php
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 消息

Laravel 的邮件功能由 Symfony Mailer 提供支持。Laravel 允许你注册自定义回调,这些回调将在发送消息之前使用 Symfony Message 实例调用。这使你有机会在发送消息之前深度自定义消息。要实现这一点,在你的 Envelope 定义上定义一个 using 参数:

php
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 选项:

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

然后,在配置可邮寄类的 content 方法中的 Content 定义时,使用 markdown 参数而不是 view 参数:

php
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 组件的同时轻松构建邮件消息:

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

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

面板组件

面板组件在背景颜色与消息其余部分略有不同的面板中渲染给定的文本块。这使你可以引起对给定文本块的注意:

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

表格组件

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

blade
<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 资源标签:

shell
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 方法接受一个电子邮件地址、一个用户实例或一个用户集合。如果你传递一个对象或对象集合,邮件程序在确定电子邮件的收件人时将自动使用它们的 emailname 属性,因此请确保这些属性在你的对象上可用。指定好收件人后,你可以将可邮寄类的实例传递给 send 方法:

php
<?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
{
    /**
     * 运送给定订单。
     */
    public function store(Request $request): RedirectResponse
    {
        $order = Order::findOrFail($request->order_id);

        // 运送订单...

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

        return redirect('/orders');
    }
}

在发送消息时,你不限于仅指定“收件人”收件人。你可以通过将各自的方法链式调用来设置“收件人”、“抄送”和“密送”收件人:

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

循环收件人

有时,你可能需要通过遍历收件人/电子邮件地址数组来向收件人列表发送可邮寄类。但是,由于 to 方法会将电子邮件地址附加到可邮寄类的收件人列表中,因此循环的每次迭代都会向之前的每个收件人发送另一封电子邮件。因此,你应该始终为每个收件人重新创建可邮寄类实例:

php
foreach (['taylor@example.com', 'dries@example.com'] as $recipient) {
    Mail::to($recipient)->send(new OrderShipped($order));
}

通过特定邮件程序发送邮件

默认情况下,Laravel 将使用在应用程序的 mail 配置文件中配置为 default 邮件程序的邮件程序发送电子邮件。但是,你可以使用 mailer 方法通过特定的邮件程序配置发送消息:

php
Mail::mailer('postmark')
    ->to($request->user())
    ->send(new OrderShipped($order));

队列邮件

队列化邮件消息

由于发送电子邮件消息可能会对应用程序的响应时间产生负面影响,许多开发人员选择将电子邮件消息排队以便在后台发送。Laravel 使用其内置的统一队列 API 使这变得容易。要将邮件消息排队,请在指定消息收件人后使用 Mail 门面的 queue 方法:

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

此方法将自动负责将任务推送到队列,以便消息在后台发送。你需要在使用此功能之前配置你的队列

延迟消息排队

如果你希望延迟发送排队的电子邮件消息,可以使用 later 方法。作为其第一个参数,later 方法接受一个 DateTime 实例,指示消息应在何时发送:

php
Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->later(now()->plus(minutes: 10), new OrderShipped($order));

推送到特定队列

由于所有使用 make:mail 命令生成的可邮寄类都使用了 Illuminate\Bus\Queueable trait,你可以在任何可邮寄类实例上调用 onQueueonConnection 方法,从而为消息指定连接和队列名称:

php
$message = (new OrderShipped($order))
    ->onConnection('sqs')
    ->onQueue('emails');

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

默认队列化

如果你有希望始终被队列化的可邮寄类,你可以在该类上实现 ShouldQueue 契约。现在,即使你在邮寄时调用了 send 方法,该可邮寄类仍将被队列化,因为它实现了该契约:

php
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderShipped extends Mailable implements ShouldQueue
{
    // ...
}

队列可邮寄类和数据库事务

当排队的可邮寄类在数据库事务中被分发时,它们可能会在数据库事务提交之前被队列处理。发生这种情况时,你在数据库事务期间对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,在事务中创建的任何模型或数据库记录可能在数据库中尚不存在。如果你的可邮寄类依赖这些模型,则当处理发送排队可邮寄类的任务时,可能会发生意外错误。

如果你的队列连接的 after_commit 配置选项设置为 false,你仍然可以通过在发送邮件消息时调用 afterCommit 方法来指示特定的排队可邮寄类应在所有打开的数据库事务提交后才被分发:

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

或者,你可以从你的可邮寄类的构造函数中调用 afterCommit 方法:

php
<?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

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

队列电子邮件失败

当排队电子邮件失败时,如果已定义,将在排队可邮寄类上调用 failed 方法。导致排队电子邮件失败的 Throwable 实例将被传递给 failed 方法:

php
<?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;

    /**
     * 处理排队电子邮件的失败。
     */
    public function failed(Throwable $exception): void
    {
        // ...
    }
}

渲染可邮寄类

有时你可能希望捕获可邮寄类的 HTML 内容而不发送它。为此,你可以调用可邮寄类的 render 方法。此方法将以字符串形式返回可邮寄类的已评估 HTML 内容:

php
use App\Mail\InvoicePaid;
use App\Models\Invoice;

$invoice = Invoice::find(1);

return (new InvoicePaid($invoice))->render();

在浏览器中预览可邮寄类

在设计可邮寄类的模板时,像典型的 Blade 模板一样在浏览器中快速预览渲染的可邮寄类是很方便的。因此,Laravel 允许你直接从路由闭包或控制器返回任何可邮寄类。当返回可邮寄类时,它将被渲染并显示在浏览器中,让你无需将其发送到真实的电子邮件地址即可快速预览其设计:

php
Route::get('/mailable', function () {
    $invoice = App\Models\Invoice::find(1);

    return new App\Mail\InvoicePaid($invoice);
});

本地化可邮寄类

Laravel 允许你使用与请求当前区域设置不同的区域设置发送可邮寄类,并且如果邮件被排队,它甚至会记住此区域设置。

要实现这一点,Mail 门面提供了一个 locale 方法来设置所需的语言。当可邮寄类的模板被评估时,应用程序将切换到此区域设置,并在评估完成后恢复到以前的区域设置:

php
Mail::to($request->user())->locale('es')->send(
    new OrderShipped($order)
);

用户首选区域设置

有时,应用程序会存储每个用户的首选区域设置。通过在一个或多个模型上实现 HasLocalePreference 契约,你可以指示 Laravel 在发送邮件时使用此存储的区域设置:

php
use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * 获取用户的首选区域设置。
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}

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

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

测试

测试可邮寄内容

Laravel 提供了多种方法来检查你的可邮寄类的结构。此外,Laravel 还提供了几种便捷的方法来测试你的可邮寄类是否包含你期望的内容:

php
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']);
});
php
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 版本包含给定的字符串,而“文本”断言断言可邮寄类的纯文本版本包含给定的字符串。

测试可邮寄发送

我们建议将可邮寄类的内容测试与你的测试分开,这些测试断言给定的可邮寄类已“发送”给特定用户。通常,可邮寄类的内容与你正在测试的代码无关,简单地断言 Laravel 被指示发送给定的可邮寄类就足够了。

你可以使用 Mail 门面的 fake 方法来防止邮件被发送。调用 Mail 门面的 fake 方法后,你可以断言可邮寄类被指示发送给用户,甚至可以检查可邮寄类接收到的数据:

php
<?php

use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

test('orders can be shipped', 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);

    // 断言一个可邮寄类被发送了两次...
    Mail::assertSentTimes(OrderShipped::class, 2);

    // 断言总共发送了 3 个可邮寄类...
    Mail::assertSentCount(3);
});
php
<?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);

        // 断言一个可邮寄类被发送了两次...
        Mail::assertSentTimes(OrderShipped::class, 2);

        // 断言总共发送了 3 个可邮寄类...
        Mail::assertSentCount(3);
    }
}

如果你正在将可邮寄类排队以便在后台发送,你应该使用 assertQueued 方法而不是 assertSent

php
Mail::assertQueued(OrderShipped::class);
Mail::assertNotQueued(OrderShipped::class);
Mail::assertNothingQueued();
Mail::assertQueuedCount(3);

你还可以使用 assertOutgoingCount 方法断言已发送或已排队的可邮寄类总数:

php
Mail::assertOutgoingCount(3);

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

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

当调用 Mail 门面的断言方法时,提供的闭包接受的可邮寄类实例公开了用于检查可邮寄类的有用方法:

php
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->hasMetadata('order_id', $mail->order->id);
           $mail->usesMailer('ses');
});

可邮寄类实例还包括几个用于检查可邮寄类附件的有用方法:

php
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 方法:

php
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 运行时,你可以在 http://localhost:8025 访问 Mailpit 界面。

使用全局 to 地址

最后,你可以通过调用 Mail 门面提供的 alwaysTo 方法来指定一个全局的“收件人”地址。通常,此方法应在应用程序的某个服务提供者的 boot 方法中调用:

php
use Illuminate\Support\Facades\Mail;

/**
 * 引导任何应用程序服务。
 */
public function boot(): void
{
    if ($this->app->environment('local')) {
        Mail::alwaysTo('taylor@example.com');
    }
}

使用 alwaysTo 方法时,邮件消息上的任何其他“抄送”或“密送”地址都将被移除。

事件

Laravel 在发送邮件消息时调度两个事件。MessageSending 事件在消息发送之前调度,而 MessageSent 事件在消息发送之后调度。请记住,这些事件是在邮件发送时调度的,而不是在排队时调度的。你可以为这些事件在你的应用程序中创建事件监听器

php
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 方法:

php
<?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
{
    /**
     * 创建一个新的 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 方法注册它。通常,这应该在应用程序的 AppServiceProviderboot 方法中完成。一个 $config 参数将被传递给提供给 extend 方法的闭包。此参数将包含在应用程序的 config/mail.php 配置文件中为邮件程序定义的配置数组:

php
use App\Mail\MailchimpTransport;
use Illuminate\Support\Facades\Mail;
use MailchimpTransactional\ApiClient;

/**
 * 引导任何应用程序服务。
 */
public function boot(): void
{
    Mail::extend('mailchimp', function (array $config = []) {
        $client = new ApiClient;

        $client->setApiKey($config['key']);

        return new MailchimpTransport($client);
    });
}

一旦你的自定义传输被定义和注册,你可以在应用程序的 config/mail.php 配置文件中创建一个利用该新传输的邮件程序定义:

php
'mailchimp' => [
    'transport' => 'mailchimp',
    'key' => env('MAILCHIMP_API_KEY'),
    // ...
],

额外的 Symfony 传输

Laravel 包含对某些现有的由 Symfony 维护的邮件传输(如 Mailgun 和 Postmark)的支持。但是,你可能希望通过支持其他由 Symfony 维护的传输来扩展 Laravel。你可以通过 Composer 安装必要的 Symfony 邮件程序并使用 Laravel 注册传输来实现。例如,你可以安装并注册“Brevo”(原“Sendinblue”)Symfony 邮件程序:

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

一旦安装了 Brevo 邮件程序包,你可以将你的 Brevo API 凭据条目添加到应用程序的 services 配置文件中:

php
'brevo' => [
    'key' => env('BREVO_API_KEY'),
],

接下来,你可以使用 Mail 门面的 extend 方法向 Laravel 注册该传输。通常,这应在服务提供者的 boot 方法中完成:

php
use Illuminate\Support\Facades\Mail;
use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory;
use Symfony\Component\Mailer\Transport\Dsn;

/**
 * 引导任何应用程序服务。
 */
public function boot(): void
{
    Mail::extend('brevo', function () {
        return (new BrevoTransportFactory)->create(
            new Dsn(
                'brevo+api',
                'default',
                config('services.brevo.key')
            )
        );
    });
}

一旦你的传输被注册,你可以在应用程序的 config/mail.php 配置文件中创建一个利用该新传输的邮件程序定义:

php
'brevo' => [
    'transport' => 'brevo',
    // ...
],