Skip to content

邮件

简介

发送邮件不必很复杂。Laravel 提供了一个简洁、简单的邮件 API,该 API 由流行的 Symfony Mailer 组件提供支持。Laravel 和 Symfony Mailer 为通过 SMTP、Mailgun、Postmark、Resend、Amazon SES 和 sendmail 发送邮件提供了驱动程序,允许你通过本地或基于云的服务快速开始发送邮件。

配置

Laravel 的邮件服务可以通过应用程序的 config/mail.php 配置文件进行配置。此文件中配置的每个邮件程序都可以有自己独特的配置,甚至可以有自己独特的"传输",允许你的应用程序使用不同的邮件服务来发送某些电子邮件消息。例如,你的应用程序可能使用 Postmark 发送交易性电子邮件,而使用 Amazon SES 发送批量电子邮件。

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

驱动程序 / 传输前提条件

基于 API 的驱动程序(如 Mailgun、Postmark、Resend 和 MailerSend)通常比通过 SMTP 服务器发送邮件更简单、更快速。如果可能,我们建议你使用这些驱动程序之一。

Mailgun 驱动程序

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

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

接下来,在应用程序的 config/mail.php 配置文件中将 default 选项设置为 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' => [
        'token' => env('POSTMARK_TOKEN'),
    ],

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

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_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 临时凭据 通过会话令牌进行交互,你可以在应用程序的 SES 配置中添加 token 键:

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

MailerSend 驱动程序

MailerSend,一种交易性电子邮件和短信服务,维护了他们自己的基于 API 的邮件驱动程序,用于 Laravel。包含驱动程序的包可以通过 Composer 包管理器安装:

shell
composer require mailersend/laravel-driver

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

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

MAILERSEND_API_KEY=your-api-key

最后,在应用程序的 config/mail.php 配置文件中的 mailers 数组中添加 MailerSend:

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

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

故障转移配置

有时,你配置的外部服务可能无法发送应用程序的邮件。在这种情况下,定义一个或多个备用邮件发送配置可能会有用,这些配置将在主发送驱动程序不可用时用于发送。

要实现这一点,你应该在应用程序的 mail 配置文件中定义一个使用 failover 传输的邮件程序。你应用程序的 failover 邮件程序的配置数组应包含一个 mailers 数组,该数组引用了应该用于发送的配置邮件程序的顺序:

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

        // ...
    ],

一旦定义了故障转移邮件程序,你应该将此邮件程序设置为应用程序默认使用的邮件程序,方法是将其名称指定为应用程序 mail 配置文件中 default 配置键的值:

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

轮询配置

roundrobin 传输允许你在多个邮件程序之间分配邮件发送工作负载。要开始使用,在应用程序的 mail 配置文件中定义一个使用 roundrobin 传输的邮件程序。你应用程序的 roundrobin 邮件程序的配置数组应包含一个 mailers 数组,该数组引用了应该用于发送的配置邮件程序:

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

        // ...
    ],

一旦定义了轮询邮件程序,你应该将此邮件程序设置为应用程序默认使用的邮件程序,方法是将其名称指定为应用程序 mail 配置文件中 default 配置键的值:

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

轮询传输会从配置邮件程序列表中随机选择一个邮件程序,然后对每个后续电子邮件切换到下一个可用的邮件程序。与 failover 传输相反,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 模板

配置发送者

使用信封

首先,让我们探讨如何配置电子邮件的发送者。或者说,电子邮件是"从"谁发送的。有两种方式可以配置发送者。首先,你可以在消息的信封上指定"from"地址:

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

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

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

此外,你还可以在应用程序的 config/mail.php 配置文件中定义全局"reply_to"地址:

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/emails 目录来存放所有的电子邮件模板;但是,你可以随意将它们放在 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 模板中的数据一样访问它:

php
    <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 模板中的数据一样访问它:

php
    <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'),
        ];
    }

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

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('backblaze', '/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 模板生成 mailable,你可以使用 make:mail Artisan 命令的 --markdown 选项:

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

然后,当在其 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>

Table 组件

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

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

发送邮件

要发送消息,请使用 MailFacade上的 to 方法。to 方法接受电子邮件地址、用户实例或用户集合。如果您传递一个对象或对象集合,则邮件程序在确定电子邮件的收件人时将自动使用其 emailname 属性,因此请确保这些属性在您的对象上可用。指定收件人后,可以将 mailable 类的实例传递给 send 方法:

php
    <?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” 收件人各自的方法链接在一起来自由设置它们:

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

循环访问收件人

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

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

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

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

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

邮件队列

邮件消息队列

由于发送电子邮件可能会对应用程序的响应时间产生负面影响,因此许多开发人员选择将电子邮件排队以进行后台发送。Laravel 使用其内置的统一队列 API 使这变得简单。要将邮件消息排队,请在指定消息的收件人后使用 Mail Facade上的 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()->addMinutes(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 协定。现在,即使你在 mail 时调用 send 方法,mailable 仍然会排队,因为它实现了 Contract:

php
    use Illuminate\Contracts\Queue\ShouldQueue;

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

排队的 Mailables 和数据库事务

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

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

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

或者,您可以从 mailable 的构造函数中调用 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

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

显示邮件

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

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

    $invoice = Invoice::find(1);

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

在浏览器中预览 Mailables

在设计 mailable 的模板时,可以像典型的 Blade 模板一样在浏览器中快速预览渲染的 mailable 是很方便的。出于这个原因,Laravel 允许你直接从 route closure 或 controller 返回任何 mailable。当一个 mailable 被返回时,它将被渲染并显示在浏览器中,允许你快速预览它的设计,而无需将其发送到实际的电子邮件地址:

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

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

本地化 Mailable

Laravel 允许你在请求当前 locale 以外的 locale 中发送 mailables,如果邮件排队,它甚至会记住这个 locale。

为此,Mail Facade提供了一种 locale 方法来设置所需的语言。当 mailable 的模板被评估时,应用程序将更改为此 locale,然后在评估完成时恢复到以前的 locale:

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

用户首选区域设置

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

php
    use Illuminate\Contracts\Translation\HasLocalePreference;

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

一旦你实现了这个接口,Laravel 将在向模型发送 mailables 和 notifications 时自动使用首选的 locale。因此,使用此接口时无需调用 locale 方法:

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

测试

测试可邮寄内容

Laravel 提供了多种方法来检查您的 mailable 的结构。此外,Laravel 还提供了几种方便的方法来测试你的 mailable 是否包含你期望的内容。这些方法是:assertSeeInHtmlassertDontSeeInHtmlassertSeeInOrderInHtmlassertSeeInTextassertDontSeeInTextassertSeeInOrderInTextassertHasAttachmentassertHasAttachedData assertHasAttachmentFromStorageassertHasAttachmentFromStorageDisk

如您所料,“HTML” 断言 mailable 的 HTML 版本包含给定的字符串,而 “text” 断言您的 mailable 的纯文本版本包含给定的字符串:

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->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']);
});
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->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']);
}

测试 Mailable Sending

我们建议将 mailable 的内容与断言给定的 mailable 已 “发送” 给特定用户的测试分开测试。通常,mailables 的内容与你正在测试的代码无关,只需断言 Laravel 被指示发送给定的 mailable 就足够了。

您可以使用 Mail Facade的 fake 方法来阻止邮件发送。在调用 Mail Facade的 fake 方法之后,你可以断言 mailables 被指示发送给用户,甚至检查 mailables 收到的数据:

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);

    // 断言 mailable 已发送两次...
    Mail::assertSent(OrderShipped::class, 2);

    // 断言已将 mailable 发送到电子邮件地址...
    Mail::assertSent(OrderShipped::class, 'example@laravel.com');

    // 断言已将 mailable 发送到多个电子邮件地址...
    Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);

    // 断言未发送 mailable...
    Mail::assertNotSent(AnotherMailable::class);

    // 断言总共发送了 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);

        // 断言 mailable 已发送两次...
        Mail::assertSent(OrderShipped::class, 2);

        // 断言已将 mailable 发送到电子邮件地址...
        Mail::assertSent(OrderShipped::class, 'example@laravel.com');

        // 断言已将 mailable 发送到多个电子邮件地址...
        Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);

        // 断言未发送 mailable...
        Mail::assertNotSent(AnotherMailable::class);

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

如果要在后台将 mailables 排队以进行传递,则应使用 assertQueued 方法而不是 assertSent:

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

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

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

当调用 Mail Facade的断言方法时,提供的闭包接受的 mailable 实例公开了检查 mailable 的有用方法:

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('...');
    });

mailable 实例还包括几个有用的方法来检查 mailable 上的附件:

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

日志驱动程序

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

HELO / Mailtrap / Mailpit

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

如果您使用的是 Laravel Sail,则可以使用 Mailpit 预览您的消息。当 Sail 运行时,您可以在以下位置访问 Mailpit 界面:http://localhost:8025

使用全局地址

最后,您可以通过调用 Mail Facade提供的 alwaysTo 方法来指定全局 “to” 地址。通常,应从应用程序的某个服务提供商的 boot 方法调用此方法:

php
    use Illuminate\Support\Facades\Mail;

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

事件

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
    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(),
            ]]);
        }

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

定义自定义传输后,您可以通过 Mail Facade提供的 extend 方法注册它。通常,这应该在应用程序的 AppServiceProvider 服务提供商的 boot 方法中完成。$config 参数将传递给提供给 extend 方法的闭包。此参数将包含在应用程序的 config/mail.php 配置文件中为 mailer 定义的配置数组:

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

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Mail::extend('mailchimp', function (array $config = []) {
            return new MailchimpTransport(/* ... */);
        });
    }

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

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

其他 Symfony 传输

Laravel 包括对一些现有的 Symfony 维护的邮件传输的支持,比如 Mailgun 和 Postmark。但是,您可能希望扩展 Laravel 以支持额外的 Symfony 维护的传输。你可以通过 Composer 要求必要的 Symfony 邮件程序,并在 Laravel 中注册传输。例如,您可以安装并注册 “Brevo” (以前称为 “Sendinblue”) Symfony 邮件程序:

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

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

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

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