Skip to content
全部文档

邮件

简介

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

配置

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

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

驱动 / 传输前置条件

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

Mailgun 驱动

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

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

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

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

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

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

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

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

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

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

Postmark 驱动

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

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

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

'postmark' => [
    'token' => env('POSTMARK_TOKEN'),
],

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

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

通过这种方式,你还可以使用不同的消息流设置多个邮戳邮件程序。

Resend 驱动

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

shell
composer require resend/resend-php

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

'resend' => [
    'key' => env('RESEND_KEY'),
],

SES 驱动

要使用 Amazon SES 驱动程序,你必须首先安装适用于 PHP 的 Amazon AWS 开发工具包。你可以通过 Composer 包管理器安装此库:

shell
composer require aws/aws-sdk-php

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

'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],

要通过会话令牌使用 AWS 临时凭证,你可以将 token 密钥添加到应用程序的 SES 配置中:

'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'token' => env('AWS_SESSION_TOKEN'),
],

要与SES的订阅管理功能交互,你可以在邮件消息的headers方法返回的数组中返回X-Ses-List-Management-Options标头:

php
/**
 * Get the message headers.
 */
public function headers(): Headers
{
    return new Headers(
        text: [
            'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic',
        ],
    );
}

如果你想定义 Laravel 在发送电子邮件时应传递给 AWS 开发工具包的 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 是事务性邮件与短信服务,维护了基于自身 API 的 Laravel 邮件驱动。可通过 Composer 包管理器安装包含该驱动的包:

shell
composer require mailersend/laravel-driver

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

ini
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 数组,该数组引用应选择配置的邮件程序进行传递的顺序:

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

    // ...
],

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

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

轮询配置

roundrobin 传输允许你在多个邮件程序之间分配邮件工作负载。首先,在应用程序的 mail 配置文件中定义一个使用 roundrobin 传输的邮件程序。应用程序的 roundrobin 邮件程序的配置数组应包含一个 mailers 数组,该数组引用应使用哪些配置的邮件程序进行传递:

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

    // ...
],

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

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

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

配置发件人

使用 Envelope

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

use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
php
/**
 * Get the message envelope.
 */
public function envelope(): Envelope
{
    return new Envelope(
        from: new Address('jeffrey@example.com', 'Jeffrey Way'),
        subject: 'Order Shipped',
    );
}

如果你愿意,你还可以指定 replyTo 地址:

return new Envelope(
    from: new Address('jeffrey@example.com', 'Jeffrey Way'),
    replyTo: [
        new Address('taylor@example.com', 'Taylor Otwell'),
    ],
    subject: 'Order Shipped',
);

使用全局 from 地址

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

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

此外,你可以在 config/mail.php 配置文件中定义全局「reply_to」地址:

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

配置视图

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

php
/**
 * Get the message content definition.
 */
public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
    );
}

INFO

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

纯文本邮件

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

php
/**
 * Get the message content definition.
 */
public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
        text: 'mail.orders.shipped-text'
    );
}

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

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

视图数据

通过公共属性

通常,你需要将一些数据传递到你的视图,以便在呈现电子邮件的 HTML 时使用。你可以通过两种方式将数据提供给你的视图。首先,在可邮寄类上定义的任何公共属性都将自动可供视图使用。因此,例如,你可以将数据传递到可邮寄类的构造函数中,并将该数据设置为该类中定义的公共属性:

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;

    /**
     * Create a new message instance.
     */
    public function __construct(
        public Order $order,
    ) {}

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: 'mail.orders.shipped',
        );
    }
}

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

<div>
    Price: &#123;&#123; $order->price &#125;&#125;
</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;

    /**
     * Create a new message instance.
     */
    public function __construct(
        protected Order $order,
    ) {}

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: 'mail.orders.shipped',
            with: [
                'orderName' => $this->order->name,
                'orderPrice' => $this->order->price,
            ],
        );
    }
}

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

<div>
    Price: &#123;&#123; $orderPrice &#125;&#125;
</div>

附件

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

use Illuminate\Mail\Mailables\Attachment;
php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromPath('/path/to/file'),
    ];
}

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

php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromPath('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}

从磁盘附加文件

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

php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorage('/path/to/file'),
    ];
}

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

php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorage('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}

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

php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorageDisk('s3', '/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}

原始数据附件

fromData 附件方法可用于将原始字节字符串作为附件附加。例如,如果你已在内存中生成 PDF 并且希望将其附加到电子邮件而不将其写入磁盘,则可以使用此方法。 fromData 方法接受一个闭包,该闭包解析原始数据字节以及附件应分配的名称:

php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromData(fn () => $this->pdf, 'Report.pdf')
            ->withMime('application/pdf'),
    ];
}

内联附件

将内联图像嵌入到电子邮件中通常很麻烦;然而,Laravel 提供了一种将图像附加到电子邮件的便捷方法。要嵌入内嵌图像,请在电子邮件模板中的 $message 变量上使用 embed 方法。 Laravel 自动使 $message 变量可用于所有电子邮件模板,因此你无需担心手动传递它:

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 接口。该接口指示你的类定义一个返回 Illuminate\Mail\Attachment 实例的 toMailAttachment 方法:

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
{
    /**
     * Get the attachable representation of the model.
     */
    public function toMailAttachment(): Attachment
    {
        return Attachment::fromPath('/path/to/file');
    }
}

定义可附加对象后,你可以在构建电子邮件时从 attachments 方法返回该对象的实例:

php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [$this->photo];
}

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

// Create an attachment from a file on your default disk...
return Attachment::fromStorage($this->path);

// Create an attachment from a file on a specific disk...
return Attachment::fromStorageDisk('backblaze', $this->path);

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

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

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

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

邮件头

有时你可能需要将附加标头附加到传出消息中。例如,你可能需要设置自定义 Message-Id 或其他任意文本标题。

要实现此目的,请在你的邮件上定义一个 headers 方法。 headers方法应该返回一个Illuminate\Mail\Mailables\Headers实例。该类接受 messageIdreferencestext 参数。当然,你可以仅提供特定消息所需的参数:

use Illuminate\Mail\Mailables\Headers;
php
/**
 * Get the message headers.
 */
public function headers(): Headers
{
    return new Headers(
        messageId: 'custom-message-id@example.com',
        references: ['previous-message@example.com'],
        text: [
            'X-Custom-Header' => 'Custom Value',
        ],
    );
}

标签与元数据

某些第三方电子邮件提供商(例如 Mailgun 和 Postmark)支持消息「标签」和「元数据」,它们可用于对应用程序发送的电子邮件进行分组和跟踪。你可以通过 Envelope 定义将标签和元数据添加到电子邮件中:

use Illuminate\Mail\Mailables\Envelope;
php
/**
 * Get the message envelope.
 *
 * @return \Illuminate\Mail\Mailables\Envelope
 */
public function envelope(): Envelope
{
    return new Envelope(
        subject: 'Order Shipped',
        tags: ['shipment'],
        metadata: [
            'order_id' => $this->order->id,
        ],
    );
}

如果你的应用程序使用 Mailgun 驱动程序,你可以查阅 Mailgun 的文档以获取有关 tagsmetadata 的更多信息。同样,也可以查阅 Postmark 文档以获取有关 tagsmetadata 支持的更多信息。

如果你的应用程序使用 Amazon SES 发送电子邮件,你应使用 metadata 方法将 SES「标签」 附加到消息。

自定义 Symfony 消息

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

use Illuminate\Mail\Mailables\Envelope;
use Symfony\Component\Mime\Email;
php
/**
 * Get the message envelope.
 */
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 参数:

use Illuminate\Mail\Mailables\Content;
php
/**
 * Get the message content definition.
 */
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>

INFO

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

Button 组件

按钮组件呈现居中的按钮链接。该组件接受两个参数,一个url和一个可选的color。支持的颜色为 primarysuccesserror。你可以根据需要向消息中添加任意数量的按钮组件:

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

Panel 组件

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

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 选项以匹配新主题的名称。

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

发送邮件

要发送消息,请使用 Mail facade 上的 to 方法。 to 方法接受电子邮件地址、用户实例或用户集合。如果你传递一个对象或对象集合,则邮件程序在确定电子邮件的收件人时将自动使用它们的 emailname 属性,因此请确保这些属性在你的对象上可用。指定收件人后,你可以将可邮寄类的实例传递给 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
{
    /**
     * Ship the given order.
     */
    public function store(Request $request): RedirectResponse
    {
        $order = Order::findOrFail($request->order_id);

        // Ship the order...

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

        return redirect('/orders');
    }
}

发送消息时,你不仅限于指定「收件人」收件人。你可以通过将各自的方法链接在一起来自由设置「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));
}

通过指定 Mailer 发送邮件

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

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

队列化邮件

将邮件消息加入队列

由于发送电子邮件会对应用程序的响应时间产生负面影响,因此许多开发人员选择将电子邮件排队以供后台发送。 Laravel 使用其内置的 统一队列 API 使这一切变得简单。要对邮件消息进行排队,请在指定消息的收件人后使用 Mail 外观上的 queue 方法:

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

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

延迟加入队列

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

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

推送到指定队列

由于使用 make:mail 命令生成的所有可邮寄类都使用 Illuminate\Bus\Queueable 特征,因此你可以在任何可邮寄类实例上调用 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()
);

或者,你可以从 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;

    /**
     * Create a new message instance.
     */
    public function __construct()
    {
        $this->afterCommit();
    }
}

INFO

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

渲染邮件类

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

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

$invoice = Invoice::find(1);

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

在浏览器中预览邮件类

设计可邮寄的模板时,可以像典型的 Blade 模板一样在浏览器中快速预览渲染的可邮寄,非常方便。因此,Laravel 允许你直接从路由关闭或控制器返回任何可邮寄的内容。当邮件返回时,它将在浏览器中呈现并显示,使你可以快速预览其设计,而无需将其发送到实际的电子邮件地址:

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

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

本地化邮件类

Laravel 允许你在请求当前区域设置之外的区域设置中发送邮件,如果邮件排队,甚至会记住该区域设置。

为了实现这一点,Mail外观提供了locale方法来设置所需的语言。当评估可邮寄模板时,应用程序将更改为此区域设置,然后在评估完成后恢复到之前的区域设置:

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

用户首选语言区域

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

use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * Get the user's preferred locale.
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}

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

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

测试

测试邮件内容

Laravel 提供了多种方法来检查邮件的结构。此外,Laravel 还提供了若干便捷方法,用于测试邮件是否包含你期望的内容。这些方法包括:assertSeeInHtmlassertDontSeeInHtmlassertSeeInOrderInHtmlassertSeeInTextassertDontSeeInTextassertSeeInOrderInTextassertHasAttachmentassertHasAttachedDataassertHasAttachmentFromStorageassertHasAttachmentFromStorageDisk

正如你所期望的,「HTML」断言断言你的可邮寄的 HTML 版本包含给定的字符串,而「文本」断言断言你的可邮寄的纯文本版本包含给定的字符串。

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

测试邮件发送

我们建议将可邮寄内容的测试与断言给定可邮寄内容「发送」给特定用户的测试分开进行。通常,可邮寄内容与你正在测试的代码无关,只需断言 Laravel 被指示发送给定可邮寄就足够了。

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

php
<?php

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

test('orders can be shipped', function () {
    Mail::fake();

    // Perform order shipping...

    // Assert that no mailables were sent...
    Mail::assertNothingSent();

    // Assert that a mailable was sent...
    Mail::assertSent(OrderShipped::class);

    // Assert a mailable was sent twice...
    Mail::assertSent(OrderShipped::class, 2);

    // Assert a mailable was sent to an email address...
    Mail::assertSent(OrderShipped::class, 'example@laravel.com');

    // Assert a mailable was sent to multiple email addresses...
    Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);

    // Assert a mailable was not sent...
    Mail::assertNotSent(AnotherMailable::class);

    // Assert 3 total mailables were sent...
    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();

        // Perform order shipping...

        // Assert that no mailables were sent...
        Mail::assertNothingSent();

        // Assert that a mailable was sent...
        Mail::assertSent(OrderShipped::class);

        // Assert a mailable was sent twice...
        Mail::assertSent(OrderShipped::class, 2);

        // Assert a mailable was sent to an email address...
        Mail::assertSent(OrderShipped::class, 'example@laravel.com');

        // Assert a mailable was sent to multiple email addresses...
        Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);

        // Assert a mailable was not sent...
        Mail::assertNotSent(AnotherMailable::class);

        // Assert 3 total mailables were sent...
        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 Facade 的断言方法时,所提供的闭包接受的可邮寄实例公开了用于检查可邮寄的有用方法:

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) {
    return $mail->hasTo($user->email) &&
           $mail->hasCc('...') &&
           $mail->hasBcc('...') &&
           $mail->hasReplyTo('...') &&
           $mail->hasFrom('...') &&
           $mail->hasSubject('...');
});

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

use Illuminate\Mail\Mailables\Attachment;

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {
    return $mail->hasAttachment(
        Attachment::fromPath('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf')
    );
});

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {
    return $mail->hasAttachment(
        Attachment::fromStorageDisk('s3', '/path/to/file')
    );
});

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) {
    return $mail->hasAttachment(
        Attachment::fromData(fn () => $pdfData, 'name.pdf')
    );
});

你可能已经注意到,有两种断言邮件未发送的方法:assertNotSentassertNotQueued。有时你可能希望断言没有邮件被发送排队。为此,你可以使用 assertNothingOutgoingassertNotOutgoing 方法:

Mail::assertNothingOutgoing();

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

邮件与本地开发

在开发发送电子邮件的应用程序时,你可能不希望实际将电子邮件发送到实时电子邮件地址。 Laravel 提供了几种在本地开发期间「禁用」实际发送电子邮件的方法。

日志驱动

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

HELO / Mailtrap / Mailpit

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

如果你使用 Laravel Sail,你可以使用 Mailpit 预览消息。当 Sail 运行时,你可以访问 Mailpit 界面:http://localhost:8025

使用全局 to 地址

最后,你可以通过调用 Mail 外观提供的 alwaysTo 方法来指定全局「收件人」地址。通常,应从应用程序服务提供商之一的 boot 方法调用此方法:

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

事件

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

use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;

class LogMessage
{
    /**
     * Handle the given event.
     */
    public function handle(MessageSending $event): void
    {
        // ...
    }
}

自定义传输

Laravel 包含多种邮件传输;但是,你可能希望编写自己的传输方式,通过 Laravel 不支持的其他服务来发送电子邮件。首先,定义一个扩展 Symfony\Component\Mailer\Transport\AbstractTransport 类的类。然后,在你的传输上实现 doSend__toString 方法:

use MailchimpTransactional\ApiClient;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\MessageConverter;

class MailchimpTransport extends AbstractTransport
{
    /**
     * Create a new Mailchimp transport instance.
     */
    public function __construct(
        protected ApiClient $client,
    ) {
        parent::__construct();
    }

    /**
     * {@inheritDoc}
     */
    protected function doSend(SentMessage $message): void
    {
        $email = MessageConverter::toEmail($message->getOriginalMessage());

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

    /**
     * Get the string representation of the transport.
     */
    public function __toString(): string
    {
        return 'mailchimp';
    }
}

定义自定义传输后,你可以通过 Mail 外观提供的 extend 方法注册它。通常,这应该在应用程序的 AppServiceProviderboot 方法中完成。 $config 参数将传递给提供给 extend 方法的闭包。该参数将包含在应用程序的 config/mail.php 配置文件中为邮件程序定义的配置数组:

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

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

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

额外的 Symfony 传输

Laravel 支持一些现有的 Symfony 维护的邮件传输,例如 Mailgun 和 Postmark。然而,你可能希望扩展 Laravel,支持额外的 Symfony 维护的传输。你可以通过 Composer 请求必要的 Symfony 邮件程序并在 Laravel 中注册传输来实现此目的。例如,你可以安装并注册「Brevo」(以前的「Sendinblue」)Symfony 邮件程序:

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

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

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

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

use Illuminate\Support\Facades\Mail;
use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory;
use Symfony\Component\Mailer\Transport\Dsn;
php
/**
 * 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',
    // ...
],