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 配置数组。该数组包含每个主要邮件驱动程序/传输的示例配置条目,而 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 消息流,可以将 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_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 是一个事务性电子邮件和 SMS 服务,为 Laravel 维护自己的基于 API 的邮件驱动程序。可以通过 Composer 包管理器安装包含该驱动程序的包:

shell
composer require mailersend/laravel-driver

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

ini
MAIL_MAILER=mailersend
MAIL_FROM_ADDRESS=app@yourdomain.com
MAIL_FROM_NAME="应用名称"

MAILERSEND_API_KEY=your-api-key

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

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

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

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

使用全局 from 地址

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

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

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

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

配置视图

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

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

您可能希望创建一个 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>
    价格: {{ $order->price }}
</div>

通过 with 参数:

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

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>
    价格: {{ $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'),
    ];
}

在向消息添加文件时,您还可以使用 as 和 / 或 withMime 方法指定附件的显示名称和 / 或 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>
    这里是一张图片:

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

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

嵌入原始数据附件

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

blade
<body>
    这里是一张来自原始数据的图片:

    <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: '订单已发货',
        tags: ['shipment'],
        metadata: [
            'order_id' => $this->order->id,
        ],
    );
}

如果您的应用程序使用 Mailgun 驱动程序,您可以查阅 Mailgun 的文档以获取有关 标签元数据 的更多信息。同样,Postmark 文档也可以查阅以获取有关其对 标签元数据 的支持的更多信息。

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

自定义 Symfony 消息

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

php
use Illuminate\Mail\Mailables\Envelope;
use Symfony\Component\Mime\Email;

/**
 * 获取消息信封。
 */
public function envelope(): Envelope
{
    return new Envelope(
        subject: '订单已发货',
        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 方法中配置邮件时,使用 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>
# 订单已发货

您的订单已发货!

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

谢谢,<br>
{{ config('app.name') }}
</x-mail::message>
lightbulb

编写 Markdown 电子邮件时,请勿使用过多的缩进。根据 Markdown 标准,Markdown 解析器将渲染缩进内容为代码块。

按钮组件

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

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

面板组件

面板组件将给定的文本块呈现在与消息其余部分略有不同的背景颜色的面板中。这使您能够引起对给定文本块的注意:

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

表格组件

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

blade
<x-mail::table>
| Laravel       | 表格         | 示例       |
| ------------- | :-----------: | ------------: |
| Col 2 是      | 居中         | $10           |
| Col 3 是      | 右对齐       | $20           |
</x-mail::table>

自定义组件

您可以将所有 Markdown 邮件组件导出到您自己的应用程序中以进行自定义。要导出组件,请使用 vendor:publish Artisan 命令发布 laravel-mail 资产标签:

shell
php artisan vendor:publish --tag=laravel-mail

此命令将把 Markdown 邮件组件发布到 resources/views/vendor/mail 目录。mail 目录将包含 htmltext 目录,每个目录包含每个可用组件的相应表示。您可以根据需要自定义这些组件。

自定义 CSS

导出组件后,resources/views/vendor/mail/html/themes 目录将包含一个 default.css 文件。您可以自定义此文件中的 CSS,您的样式将自动转换为 Markdown 邮件消息的 HTML 表示中的内联 CSS 样式。

如果您想为 Laravel 的 Markdown 组件构建一个全新的主题,您可以将 CSS 文件放置在 html/themes 目录中。在命名和保存 CSS 文件后,请更新您应用程序的 config/mail.php 配置文件中的主题选项,以匹配您新主题的名称。

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

发送邮件

要发送消息,请使用 Mail 门面 上的 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
{
    /**
     * 发货给定的订单。
     */
    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 使这变得简单。要排队邮件消息,请在指定消息收件人后使用 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 特性,您可以在任何邮件类实例上调用 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();
    }
}
lightbulb

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

渲染邮件

有时,您可能希望在不发送的情况下捕获邮件的 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 提供了几种方便的方法来测试您的邮件是否包含您期望的内容。这些方法包括: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('发票已支付');
    $mailable->assertHasTag('example-tag');
    $mailable->assertHasMetadata('key', 'value');

    $mailable->assertSeeInHtml($user->email);
    $mailable->assertSeeInHtml('发票已支付');
    $mailable->assertSeeInOrderInHtml(['发票已支付', '谢谢']);

    $mailable->assertSeeInText($user->email);
    $mailable->assertSeeInOrderInText(['发票已支付', '谢谢']);

    $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('发票已支付');
    $mailable->assertHasTag('example-tag');
    $mailable->assertHasMetadata('key', 'value');

    $mailable->assertSeeInHtml($user->email);
    $mailable->assertSeeInHtml('发票已支付');
    $mailable->assertSeeInOrderInHtml(['发票已支付', '谢谢']);

    $mailable->assertSeeInText($user->email);
    $mailable->assertSeeInOrderInText(['发票已支付', '谢谢']);

    $mailable->assertHasAttachment('/path/to/file');
    $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));
    $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);
    $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
    $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
}

测试邮件发送

我们建议将邮件内容的测试与断言特定用户“发送”给定邮件的测试分开。通常,邮件的内容与您正在测试的代码无关,仅仅断言 Laravel 被指示向特定用户发送给定邮件就足够了。

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

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

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

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

如果您在后台排队邮件,则应使用 assertQueued 方法而不是 assertSent

php
Mail::assertQueued(OrderShipped::class);
Mail::assertNotQueued(OrderShipped::class);
Mail::assertNothingQueued();
Mail::assertQueuedCount(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('...');
});

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

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

或者,您可以使用 HELOMailtrapsmtp 驱动程序将电子邮件消息发送到“虚拟”邮箱,您可以在真实的电子邮件客户端中查看它们。这种方法的好处是允许您在 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');
    }
}

事件

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

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

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

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

/**
 * 启动任何应用程序服务。
 */
public function boot(): void
{
    Mail::extend('mailchimp', function (array $config = []) {
        return new MailchimpTransport(/* ... */);
    });
}

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

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

附加 Symfony 传输

Laravel 包含对一些现有 Symfony 维护的邮件传输的支持,如 Mailgun 和 Postmark。但是,您可能希望通过 Composer 安装必要的 Symfony 邮件传输并将其注册到 Laravel,以扩展 Laravel 对其他 Symfony 维护的传输的支持。例如,您可以安装并注册“Brevo”(以前称为“Sendinblue”)Symfony 邮件传输:

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

安装 Brevo 邮件包后,您可以在应用程序的 services 配置文件中添加 Brevo API 凭据的条目:

php
'brevo' => [
    'key' => 'your-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',
    // ...
],