翻译进度
25
分块数量
6
参与人数

发送邮件

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


邮件

简介

发送邮件并不复杂。 Laravel 提供了一个简洁的邮件 API ,由流行的 Symfony Mailer 组件提供支持。 Laravel 和 Symfony Mailer 提供了通过 SMTP 、 Mailgun 、 Postmark 、 Resend 、 Amazon SES 和 sendmail 发送邮件的驱动程序,使您能够快速开始通过本地或云服务发送邮件。

配置

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

dszhxb 翻译于 4天前

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

驱动 / 传输前提条件

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

Mailgun 驱动

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

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

dszhxb 翻译于 4天前

Postmark 驱动

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

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

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

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

如果你想为特定邮件发送器指定 Postmark 消息流,可以在邮件发送器的配置数组中添加 message_stream_id 配置选项。该配置数组位于应用程序的 config/mail.php 配置文件中:

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

这样你还可以设置多个使用不同消息流的 Postmark 邮件发送器。

Resend 驱动

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

composer require resend/resend-php

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

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

SES 驱动

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

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'),
],
陈卿诺语 翻译于 1个月前

要使用 AWS 临时凭证 通过会话令牌进行身份验证,可以在应用程序的 SES 配置中添加 token 键:

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

若要使用 SES 的 订阅管理功能,可以在邮件消息的 headers 方法返回的数组中包含 X-Ses-List-Management-Options 头信息:

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

如果需要定义 Laravel 在发送邮件时应传递给 AWS SDK SendEmail 方法的 额外选项 ,可以在 ses 配置中定义 options 数组:

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

MailerSend 驱动

MailerSend作为一款事务性邮件和短信服务,为 Laravel 维护了专门的 API 邮件驱动包。可以通过 Composer 安装该驱动包:

composer require mailersend/laravel-driver

安装完成后,在应用程序的 .env 文件中添加 MAILERSEND_API_KEY 环境变量。同时需要将 MAIL_MAILER 环境变量定义为 mailersend:

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

MAILERSEND_API_KEY=your-api-key
陈卿诺语 翻译于 1个月前

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

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

    // ...
],
陈卿诺语 翻译于 1个月前

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

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

轮询传输会从已配置的邮件程序列表中随机选择一个邮件程序,然后在后续每封邮件中切换到下一个可用邮件程序。 与旨在实现 高可用性failover 传输不同, roundrobin 传输提供的是 负载均衡

生成可邮寄类

在构建 Laravel 应用时,应用发送的每种邮件类型都表示为一个「可邮寄」类。 这些类存储在 app/Mail 目录中。 如果您在应用中没有看到此目录,不必担心,因为它会在您使用 make:mail Artisan 命令创建第一个可邮寄类时自动生成:

php artisan make:mail OrderShipped

编写可邮寄类

生成可邮寄类后,打开它以便我们可以查看其内容。 可邮寄类的配置通过多个方法完成,包括 envelopecontentattachments 方法。

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

dszhxb 翻译于 4天前

配置发件人

使用信封配置

首先,让我们来探索如何配置邮件的发件人。 或者说,邮件将「来自」谁。 有两种方式可以配置发件人。 首先,您可以在消息的信封上指定「发件人」地址:

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

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

使用全局 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' => '应用名称'],

配置视图

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

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

[!注意]
您可能希望创建一个 resources/views/emails 目录来存放所有的邮件模板; 但是,您也可以自由地将它们放在 resources/views 目录中的任何位置。

dszhxb 翻译于 3天前

纯文本邮件

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

/**
 * 获取消息内容定义
 */
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

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',
        );
    }
}
dszhxb 翻译于 3天前

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

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

通过 with 参数传递:

如果您想在将电子邮件数据发送到模板之前自定义其格式,您可以通过 Content 定义的 with 参数手动将数据传递给视图。通常,您仍将通过mailable类的构造函数传递数据;但是,您应该将此数据设置为 protectedprivate 属性,这样数据就不会自动提供给模板:

<?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: {{ $orderPrice }}
</div>

附件

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

use Illuminate\Mail\Mailables\Attachment;

/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromPath('/path/to/file'),
    ];
}
陈卿诺语 翻译于 1个月前

为邮件添加附件时,您还可以通过 aswithMime 方法分别指定附件的显示名称和MIME类型:

/**
 * 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 方法附加:

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

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

/**
 * 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 方法:

/**
 * 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文件且不希望写入磁盘时,可以使用此方法。该方法接收一个返回原始数据的闭包,以及附件名称:

/**
 * 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'),
    ];
}
陈卿诺语 翻译于 1个月前

Inline Attachments

Embedding inline images into your emails is typically cumbersome; however, Laravel provides a convenient way to attach images to your emails. To embed an inline image, use the embed method on the $message variable within your email template. Laravel automatically makes the $message variable available to all of your email templates, so you don't need to worry about passing it in manually:

<body>
    Here is an image:

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

[!WARNING]
The $message variable is not available in plain-text message templates since plain-text messages do not utilize inline attachments.

Embedding Raw Data Attachments

If you already have a raw image data string you wish to embed into an email template, you may call the embedData method on the $message variable. When calling the embedData method, you will need to provide a filename that should be assigned to the embedded image:

<body>
    Here is an image from raw data:

    <img src="{{ $message->embedData($data, 'example-image.jpg') }}">
</body>

Attachable Objects

While attaching files to messages via simple string paths is often sufficient, in many cases the attachable entities within your application are represented by classes. For example, if your application is attaching a photo to a message, your application may also have a Photo model that represents that photo. When that is the case, wouldn't it be convenient to simply pass the Photo model to the attach method? Attachable objects allow you to do just that.

To get started, implement the Illuminate\Contracts\Mail\Attachable interface on the object that will be attachable to messages. This interface dictates that your class defines a toMailAttachment method that returns an Illuminate\Mail\Attachment instance:

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

Once you have defined your attachable object, you may return an instance of that object from the attachments method when building an email message:

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

Of course, attachment data may be stored on a remote file storage service such as Amazon S3. So, Laravel also allows you to generate attachment instances from data that is stored on one of your application's filesystem disks:

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

In addition, you may create attachment instances via data that you have in memory. To accomplish this, provide a closure to the fromData method. The closure should return the raw data that represents the attachment:

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

Laravel also provides additional methods that you may use to customize your attachments. For example, you may use the as and withMime methods to customize the file's name and MIME type:

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

Headers

Sometimes you may need to attach additional headers to the outgoing message. For instance, you may need to set a custom Message-Id or other arbitrary text headers.

To accomplish this, define a headers method on your mailable. The headers method should return an Illuminate\Mail\Mailables\Headers instance. This class accepts messageId, references, and text parameters. Of course, you may provide only the parameters you need for your particular message:

use Illuminate\Mail\Mailables\Headers;

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

Tags and Metadata

Some third-party email providers such as Mailgun and Postmark support message "tags" and "metadata", which may be used to group and track emails sent by your application. You may add tags and metadata to an email message via your Envelope definition:

use Illuminate\Mail\Mailables\Envelope;

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

If your application is using the Mailgun driver, you may consult Mailgun's documentation for more information on tags and metadata. Likewise, the Postmark documentation may also be consulted for more information on their support for tags and metadata.

If your application is using Amazon SES to send emails, you should use the metadata method to attach SES "tags" to the message.

Customizing the Symfony Message

Laravel's mail capabilities are powered by Symfony Mailer. Laravel allows you to register custom callbacks that will be invoked with the Symfony Message instance before sending the message. This gives you an opportunity to deeply customize the message before it is sent. To accomplish this, define a using parameter on your Envelope definition:

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

/**
 * Get the message envelope.
 */
public function envelope(): Envelope
{
    return new Envelope(
        subject: 'Order Shipped',
        using: [
            function (Email $message) {
                // ...
            },
        ]
    );
}

Markdown Mailables

Markdown mailable messages allow you to take advantage of the pre-built templates and components of mail notifications in your mailables. Since the messages are written in Markdown, Laravel is able to render beautiful, responsive HTML templates for the messages while also automatically generating a plain-text counterpart.

Generating Markdown Mailables

To generate a mailable with a corresponding Markdown template, you may use the --markdown option of the make:mail Artisan command:

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

Then, when configuring the mailable Content definition within its content method, use the markdown parameter instead of the view parameter:

use Illuminate\Mail\Mailables\Content;

/**
 * Get the message content definition.
 */
public function content(): Content
{
    return new Content(
        markdown: 'mail.orders.shipped',
        with: [
            'url' => $this->orderUrl,
        ],
    );
}

Writing Markdown Messages

Markdown mailables use a combination of Blade components and Markdown syntax which allow you to easily construct mail messages while leveraging Laravel's pre-built email UI components:

<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]
Do not use excess indentation when writing Markdown emails. Per Markdown standards, Markdown parsers will render indented content as code blocks.

Button Component

The button component renders a centered button link. The component accepts two arguments, a url and an optional color. Supported colors are primary, success, and error. You may add as many button components to a message as you wish:

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

Panel Component

The panel component renders the given block of text in a panel that has a slightly different background color than the rest of the message. This allows you to draw attention to a given block of text:

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

Table Component

The table component allows you to transform a Markdown table into an HTML table. The component accepts the Markdown table as its content. Table column alignment is supported using the default Markdown table alignment syntax:

<x-mail::table>
| Laravel       | Table         | Example       |
| ------------- | :-----------: | ------------: |
| Col 2 is      | Centered      | $10           |
| Col 3 is      | Right-Aligned | $20           |
</x-mail::table>

Customizing the Components

You may export all of the Markdown mail components to your own application for customization. To export the components, use the vendor:publish Artisan command to publish the laravel-mail asset tag:

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

This command will publish the Markdown mail components to the resources/views/vendor/mail directory. The mail directory will contain an html and a text directory, each containing their respective representations of every available component. You are free to customize these components however you like.

Customizing the CSS

After exporting the components, the resources/views/vendor/mail/html/themes directory will contain a default.css file. You may customize the CSS in this file and your styles will automatically be converted to inline CSS styles within the HTML representations of your Markdown mail messages.

If you would like to build an entirely new theme for Laravel's Markdown components, you may place a CSS file within the html/themes directory. After naming and saving your CSS file, update the theme option of your application's config/mail.php configuration file to match the name of your new theme.

To customize the theme for an individual mailable, you may set the $theme property of the mailable class to the name of the theme that should be used when sending that mailable.

Sending Mail

To send a message, use the to method on the Mail facade. The to method accepts an email address, a user instance, or a collection of users. If you pass an object or collection of objects, the mailer will automatically use their email and name properties when determining the email's recipients, so make sure these attributes are available on your objects. Once you have specified your recipients, you may pass an instance of your mailable class to the send method:

<?php

namespace App\Http\Controllers;

use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class OrderShipmentController extends Controller
{
    /**
     * 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');
    }
}

You are not limited to just specifying the "to" recipients when sending a message. You are free to set "to", "cc", and "bcc" recipients by chaining their respective methods together:

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

Looping Over Recipients

Occasionally, you may need to send a mailable to a list of recipients by iterating over an array of recipients / email addresses. However, since the to method appends email addresses to the mailable's list of recipients, each iteration through the loop will send another email to every previous recipient. Therefore, you should always re-create the mailable instance for each recipient:

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

Sending Mail via a Specific Mailer

By default, Laravel will send email using the mailer configured as the default mailer in your application's mail configuration file. However, you may use the mailer method to send a message using a specific mailer configuration:

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

Queueing Mail

Queueing a Mail Message

Since sending email messages can negatively impact the response time of your application, many developers choose to queue email messages for background sending. Laravel makes this easy using its built-in unified queue API. To queue a mail message, use the queue method on the Mail facade after specifying the message's recipients:

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

This method will automatically take care of pushing a job onto the queue so the message is sent in the background. You will need to configure your queues before using this feature.

Delayed Message Queueing

If you wish to delay the delivery of a queued email message, you may use the later method. As its first argument, the later method accepts a DateTime instance indicating when the message should be sent:

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

Pushing to Specific Queues

Since all mailable classes generated using the make:mail command make use of the Illuminate\Bus\Queueable trait, you may call the onQueue and onConnection methods on any mailable class instance, allowing you to specify the connection and queue name for the message:

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

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

Queueing by Default

If you have mailable classes that you want to always be queued, you may implement the ShouldQueue contract on the class. Now, even if you call the send method when mailing, the mailable will still be queued since it implements the contract:

use Illuminate\Contracts\Queue\ShouldQueue;

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

Queued Mailables and Database Transactions

When queued mailables are dispatched within database transactions, they may be processed by the queue before the database transaction has committed. When this happens, any updates you have made to models or database records during the database transaction may not yet be reflected in the database. In addition, any models or database records created within the transaction may not exist in the database. If your mailable depends on these models, unexpected errors can occur when the job that sends the queued mailable is processed.

If your queue connection's after_commit configuration option is set to false, you may still indicate that a particular queued mailable should be dispatched after all open database transactions have been committed by calling the afterCommit method when sending the mail message:

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

Alternatively, you may call the afterCommit method from your mailable's constructor:

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

[!NOTE]
To learn more about working around these issues, please review the documentation regarding queued jobs and database transactions.

Rendering Mailables

Sometimes you may wish to capture the HTML content of a mailable without sending it. To accomplish this, you may call the render method of the mailable. This method will return the evaluated HTML content of the mailable as a string:

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

$invoice = Invoice::find(1);

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

Previewing Mailables in the Browser

When designing a mailable's template, it is convenient to quickly preview the rendered mailable in your browser like a typical Blade template. For this reason, Laravel allows you to return any mailable directly from a route closure or controller. When a mailable is returned, it will be rendered and displayed in the browser, allowing you to quickly preview its design without needing to send it to an actual email address:

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

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

Localizing Mailables

Laravel allows you to send mailables in a locale other than the request's current locale, and will even remember this locale if the mail is queued.

To accomplish this, the Mail facade offers a locale method to set the desired language. The application will change into this locale when the mailable's template is being evaluated and then revert back to the previous locale when evaluation is complete:

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

User Preferred Locales

Sometimes, applications store each user's preferred locale. By implementing the HasLocalePreference contract on one or more of your models, you may instruct Laravel to use this stored locale when sending mail:

use Illuminate\Contracts\Translation\HasLocalePreference;

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

Once you have implemented the interface, Laravel will automatically use the preferred locale when sending mailables and notifications to the model. Therefore, there is no need to call the locale method when using this interface:

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

Testing

Testing Mailable Content

Laravel provides a variety of methods for inspecting your mailable's structure. In addition, Laravel provides several convenient methods for testing that your mailable contains the content that you expect. These methods are: assertSeeInHtml, assertDontSeeInHtml, assertSeeInOrderInHtml, assertSeeInText, assertDontSeeInText, assertSeeInOrderInText, assertHasAttachment, assertHasAttachedData, assertHasAttachmentFromStorage, and assertHasAttachmentFromStorageDisk.

As you might expect, the "HTML" assertions assert that the HTML version of your mailable contains a given string, while the "text" assertions assert that the plain-text version of your mailable contains a given string:

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

Testing Mailable Sending

We suggest testing the content of your mailables separately from your tests that assert that a given mailable was "sent" to a specific user. Typically, the content of mailables is not relevant to the code you are testing, and it is sufficient to simply assert that Laravel was instructed to send a given mailable.

You may use the Mail facade's fake method to prevent mail from being sent. After calling the Mail facade's fake method, you may then assert that mailables were instructed to be sent to users and even inspect the data the mailables received:

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

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

If you are queueing mailables for delivery in the background, you should use the assertQueued method instead of assertSent:

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

You may pass a closure to the assertSent, assertNotSent, assertQueued, or assertNotQueued methods in order to assert that a mailable was sent that passes a given "truth test". If at least one mailable was sent that passes the given truth test then the assertion will be successful:

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

When calling the Mail facade's assertion methods, the mailable instance accepted by the provided closure exposes helpful methods for examining the mailable:

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

The mailable instance also includes several helpful methods for examining the attachments on a mailable:

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

You may have noticed that there are two methods for asserting that mail was not sent: assertNotSent and assertNotQueued. Sometimes you may wish to assert that no mail was sent or queued. To accomplish this, you may use the assertNothingOutgoing and assertNotOutgoing methods:

Mail::assertNothingOutgoing();

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

Mail and Local Development

When developing an application that sends email, you probably don't want to actually send emails to live email addresses. Laravel provides several ways to "disable" the actual sending of emails during local development.

Log Driver

Instead of sending your emails, the log mail driver will write all email messages to your log files for inspection. Typically, this driver would only be used during local development. For more information on configuring your application per environment, check out the configuration documentation.

HELO / Mailtrap / Mailpit

Alternatively, you may use a service like HELO or Mailtrap and the smtp driver to send your email messages to a "dummy" mailbox where you may view them in a true email client. This approach has the benefit of allowing you to actually inspect the final emails in Mailtrap's message viewer.

If you are using Laravel Sail, you may preview your messages using Mailpit. When Sail is running, you may access the Mailpit interface at: http://localhost:8025.

Using a Global to Address

Finally, you may specify a global "to" address by invoking the alwaysTo method offered by the Mail facade. Typically, this method should be called from the boot method of one of your application's service providers:

use Illuminate\Support\Facades\Mail;

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

Events

Laravel dispatches two events while sending mail messages. The MessageSending event is dispatched prior to a message being sent, while the MessageSent event is dispatched after a message has been sent. Remember, these events are dispatched when the mail is being sent, not when it is queued. You may create event listeners for these events within your application:

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

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

Custom Transports

Laravel includes a variety of mail transports; however, you may wish to write your own transports to deliver email via other services that Laravel does not support out of the box. To get started, define a class that extends the Symfony\Component\Mailer\Transport\AbstractTransport class. Then, implement the doSend and __toString() methods on your transport:

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

Once you've defined your custom transport, you may register it via the extend method provided by the Mail facade. Typically, this should be done within the boot method of your application's AppServiceProvider service provider. A $config argument will be passed to the closure provided to the extend method. This argument will contain the configuration array defined for the mailer in the application's config/mail.php configuration file:

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

Once your custom transport has been defined and registered, you may create a mailer definition within your application's config/mail.php configuration file that utilizes the new transport:

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

Additional Symfony Transports

Laravel includes support for some existing Symfony maintained mail transports like Mailgun and Postmark. However, you may wish to extend Laravel with support for additional Symfony maintained transports. You can do so by requiring the necessary Symfony mailer via Composer and registering the transport with Laravel. For example, you may install and register the "Brevo" (formerly "Sendinblue") Symfony mailer:

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

Once the Brevo mailer package has been installed, you may add an entry for your Brevo API credentials to your application's services configuration file:

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

Next, you may use the Mail facade's extend method to register the transport with Laravel. Typically, this should be done within the boot method of a service provider:

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

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Mail::extend('brevo', function () {
        return (new BrevoTransportFactory)->create(
            new Dsn(
                'brevo+api',
                'default',
                config('services.brevo.key')
            )
        );
    });
}

Once your transport has been registered, you may create a mailer definition within your application's config/mail.php configuration file that utilizes the new transport:

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

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:6
讨论数量: 0
发起讨论 只看当前版本


暂无话题~