Laravel 邮件

简介

Laravel 基于 SwiftMailer 库提供了一套干净、清爽的邮件 API。Laravel 为 SMTP、Mailgun、Postmark、SparkPost、Amazon SES、以及 sendmail 提供了相应的驱动,从而允许你快速通过本地或云服务发送邮件。

配置

Laravel 邮件服务可以通过 mail 配置文件进行配置,该文件中的每个邮件配置都有自己的选项,以及独立的「transport」,从而允许应用使用不同的邮件服务发送特定邮件消息。例如,你的应用可能使用 Postmark 发送交易邮件,使用 Amazon SES 发送批量邮件。

安装驱动

基于 API 的驱动如 Mailgun、SparkPost 和 Postmark 通常比 SMTP 服务器更简单、更快,所以如果可以的话,尽可能使用这些服务。所有的 API 驱动要求应用已经安装 Guzzle HTTP 库,你可以通过 Composer 包管理器来安装它:

$ composer require guzzlehttp/guzzle

Mailgun 驱动

要使用 Mailgun 驱动,首先安装 Guzzle,然后在配置文件 config/mail.php 中设置 default 选项为 mailgun。接下来,验证配置文件 config/services.php 包含如下选项:

'mailgun' => [
    'domain' => 'your-mailgun-domain',
    'secret' => 'your-mailgun-key',
],
如果你不是使用「美国」区域的 Mailgun,可以在 services 配置文件中定义区域端点:

'mailgun' => [
    'domain' => 'your-mailgun-domain',
    'secret' => 'your-mailgun-key',
    'endpoint' => 'api.eu.mailgun.net',
],

Postmark 驱动

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

$ composer require wildbit/swiftmailer-postmark

接下来,安装 Guzzle 并在配置文件 config/mail.php 中设置 default 选项值为 postmark。最后,确保配置文件 config/services.php 中包含以下选项:

'postmark' => [
    'token' => 'your-postmark-token',
],

SES 驱动

要使用 Amazon SES 驱动,先安装 Amazon AWS 的 PHP SDK,你可以通过添加如下行到 composer.json 文件的 require 部分然后运行 composer update 命令来安装该库:

"aws/aws-sdk-php": "~3.0"

接下来,设置配置文件 config/mail.php 中的 default 选项为 ses。然后,验证配置文件 config/services.php 包含如下选项:

'ses' => [
    'key' => 'your-ses-key',
    'secret' => 'your-ses-secret',
    'region' => 'ses-region',  // e.g. us-east-1
],

如果我们需要在执行 SES SendRawEmail 请求时引入额外的选项,可以在 ses 配置中通过定义 options 数组来完成:

'ses' => [
    'key' => 'your-ses-key',
    'secret' => 'your-ses-secret',
    'region' => 'ses-region',  // e.g. us-east-1
    'options' => [
        'ConfigurationSetName' => 'MyConfigurationSet',
        'Tags' => [
            [
                'Name' => 'foo',
                'Value' => 'bar',
            ],
        ],
    ],
],

注: 以上驱动仅供参考,可以用来作为添加邮件驱动的例子。实际使用的时候我们基本上不使用上面的驱动


生成可邮寄类

在 Laravel 中,应用发送的每一封邮件都可以表示为“可邮寄”类,这些类都存放在 app/Mail 目录。如果没看到这个目录,别担心,它将会在你使用 make:mail 命令创建第一个可邮寄类时生成:

$ php artisan make:mail OrderShipped

编写可邮寄类

所有的可邮寄类配置都在 build 方法中完成,在这个方法中,我们可以调用多个方法,例如 from、subject、view 和 attach 来配置邮件的内容和发送。

配置发件人

使用 from 方法

首先,我们来看一下邮件发件人的配置,或者,换句话说,邮件来自于谁。有两种方式来配置发送者,第一种方式是在可邮寄类的 build 方法方法中调用 from 方法:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->from('example@example.com')
        ->view('emails.orders.shipped');
}

使用全局的 from 地址

不过,如果我们的应用在所有邮件中都使用相同的发送地址,在每个生成的可邮寄类中都调用 from 方法就显得很累赘。取而代之地,我们可以在配置文件 config/mail.php 中指定一个全局的发送地址,该地址可用于在所有可邮寄类中没有指定其它发送地址的场景下(即作为默认发件人):

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

此外,我们可以在配置文件 config/mail.php 中定义全局的「回复」地址:

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

配置视图

我们可以在可邮寄类的 build 方法中使用 view 方法来指定渲染邮件内容时使用哪个视图模板,由于每封邮件通常使用 Blade 模板来渲染内容,所以你可以在构建邮件 HTML 时使用 Blade 模板引擎提供的所有功能:

/**
 * Build the message.
 *
 * @return $this
 * @translator laravelacademy.org
 */
public function build()
{
    return $this->view('emails.orders.shipped');
}

注:我们可以创建一个 resources/views/emails 目录来存放所有邮件模板,当然,你也可以将邮件模板放到 resources/views 目录下任意其它位置。

纯文本邮件

如果我们想要定义一个纯文本格式的邮件,可以使用 text 方法。和 view 方法一样,text 方法接收一个用于渲染邮件内容的模板名,你既可以定义纯文本消息也可以定义 HTML 消息:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->view('emails.orders.shipped')
                ->text('emails.orders.shipped_plain');
}

视图数据

通过公共属性

通常,我们需要传递一些数据到渲染邮件的 HTML 视图以供使用。有两种方式将数据传递到视图,第一种是可邮寄类的公共(public)属性在视图中自动生效,举个例子,我们将数据传递给可邮寄类的构造器并将数据设置给该类的公共属性:

<?php
namespace App\Mail;

use App\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The order instance.
     *
     * @var Order
     */
    public $order;

    /**
     * Create a new message instance.
     *
     * @param  \App\Order  $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.orders.shipped');
    }
}

数据被设置给公共属性后,将会在视图中自动生效,所以你可以像在 Blade 模板中访问其它数据一样访问它们:

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

通过 with 方法

如果我们想要在数据发送到模板之前自定义邮件数据的格式,可以通过 with 方法手动传递数据到视图。一般情况下,你还是需要通过可邮寄类的构造器传递数据,不过,这次你需要设置数据为 protected 或 private 属性,这样,这些数据就不会在视图中自动生效。然后,当调用 with 方法时,传递数组数据到该方法以便数据在视图模板中生效:

<?php

namespace App\Mail;

use App\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The order instance.
     *
     * @var Order
     */
    protected $order;

    /**
     * Create a new message instance.
     *
     * @param  \App\Order $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.orders.shipped')
                    ->with([
                        'orderName' => $this->order->name,
                        'orderPrice' => $this->order->price,
                    ]);
    }
}

数据通过 with 方法传递到视图后,将会在视图中自动生效,因此我们也可以像在 Blade 模板访问其它数据一样访问传递过来的数据:

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

附件

要在邮件中添加附件,可以在可邮寄类的 build 方法中使用 attach 方法。attach 方法接收完整的文件路径作为第一个参数:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->view('emails.orders.shipped')
        ->attach('/path/to/file');
}

附加文件到消息时,还可以通过传递一个数组作为 attach 方法的第二个参数来指定文件显示名或 MIME 类型:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->view('emails.orders.shipped')
        ->attach('/path/to/file', [
            'as' => 'name.pdf',
            'mime' => 'application/pdf',
        ]);
}

添加磁盘文件

如果我们已经在某个文件系统磁盘中存储了文件,可以使用 attachFromStorage 方法将其添加到邮件:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
   return $this->view('email.orders.shipped')
               ->attachFromStorage('/path/to/file');
}

如果必要的话,我们可以通过传递第二个和第三个参数到 attachFromStorage 方法来分别指定文件的附件名以及额外的选项:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
   return $this->view('email.orders.shipped')
               ->attachFromStorage('/path/to/file', 'name.pdf', [
                   'mime' => 'application/pdf'
               ]);
}

如果我们想要指定某个存储磁盘而非默认磁盘,可以使用attachFromStorageDisk 方法:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
   return $this->view('email.orders.shipped')
               ->attachFromStorageDisk('s3', '/path/to/file');
}

原生数据附件

attachData 方法可用于添加原生的字节字符串作为附件。如果你想在内存中生成 PDF,并且在不保存到磁盘的情况下将其添加到邮件作为附件,则可以使用该方法。attachData 方法接收数据字节作为第一个参数,文件名作为第二个参数,可选数组作为第三个参数:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->view('emails.orders.shipped')
        ->attachData($this->pdf, 'name.pdf', [
            'mime' => 'application/pdf',
        ]);
}

内联附件

嵌套内联图片到邮件中通常是很笨重的,为此,Laravel 提供了便捷的方式附加图片到邮件并获取相应的 CID,要嵌入内联图片,在邮件视图中使用 $message 变量上的embed 方法即可。Laravel 在所有邮件视图中注入 $message 变量并使其自动有效,所以你不用关心如何将这个变量手动传入视图:

<body>
    Here is an image:
    <img src="{{ $message->embed($pathToFile) }}">
</body>

注:$message 变量在纯文本消息中无效,因为纯文本消息不使用内联附件。

嵌入原生数据附件

如果我们已经有一个想要嵌入邮件模板的原生数据字符串,可以使用 $message 变量上的 embedData 方法:

<body>
    Here is an image from raw data:
    <img src="{{ $message->embedData($data, $name) }}">
</body>

自定义 SwiftMailer 消息

我们可以使用 Mailable 基类上的 withSwiftMessage 方法注册一个在发送消息之前可以被原生 SwiftMailer 消息实例调用的回调。这赋予了我们在发送消息前定制消息的机会:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    $this->view('emails.orders.shipped');
​
    $this->withSwiftMessage(function ($message) {
        $message->getHeaders()
                ->addTextHeader('Custom-Header', 'HeaderValue');
    });
}

Markdown 可邮寄类

Markdown 邮件消息允许你在可邮寄类中利用预置模板和邮件通知组件。因为这些消息以 Markdown 格式编写,Laravel 还可以为它们渲染出高颜值、响应式的 HTML 模板,同时自动生成纯文本的副本。

生成 Markdown 可邮寄类

要生成带有相应 Markdown 模板的可邮寄类,可以在使用 Artisan 命令 make:mail 时带上 --markdown 选项:

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

然后,配置可邮寄类的 build 方法时,使用 markdown 方法取代 view 方法。markdown 方法接收 Markdown 模板的名称和一个可选的在模板中生效的数组数据:

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->from('example@example.com')
                ->markdown('emails.orders.shipped');
}

编写 Markdown 消息

Markdown 邮件类组合使用了 Blade 组件和 Markdown 语法,从而让你在不脱离 Laravel 预置组件的情况下轻松构建邮件消息:

@component('mail::message')
# Order Shipped
Your order has been shipped!
@component('mail::button', ['url' => $url])
View Order
@endcomponent
Thanks,<br>
{{ config('app.name') }}
@endcomponent

注:在编写 Markdown 邮件时不要使用多余的缩进,Markdown 解析器会将缩进渲染成代码区块。

按钮组件

按钮组件渲染一个居中的按钮链接。该组件接收两个参数,url 和可选的 color,支持的颜色有 primary,success 和 error。你可以添加任意数量的按钮组件到消息中:

@component('mail::button', ['url' => $url, 'color' => 'success'])
View Order
@endcomponent

面板组件

面板组件将给定的文字区块渲染到一个面板中,并且有一个淡淡的背景色与周围的消息区分开。适用于需要引起注意的文字区块:

@component('mail::panel')
This is the panel content.
@endcomponent

表格组件

表格组件允许我们将一个 Markdown 表格转化为 HTML 表格。该组件接收 Markdown 表格作为其内容。表格列对齐支持使用默认的 Markdown 表格列对齐语法:

@component('mail::table')
| Laravel       | Table         | Example  |
| ------------- |:-------------:| --------:|
| Col 2 is      | Centered      | $10      |
| Col 3 is      | Right-Aligned | $20      |
@endcomponent

自定义组件

我们可以导出所有 Markdown 邮件组件到自己的应用中进行自定义,使用 Artisan 命令 vendor:publish 来发布 laravel-mail 资源标签:

$ 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 样式就会自动调整。

如果我们想要为 Markdown 组件构建全新的主题,只需在 html/themes 目录中编写一个新的 CSS 文件并修改 mail 配置文件的 theme 选项即可。

要为独立的可邮寄类自定义主题,可以通过设置可邮寄类的 $theme 属性值为对应的主题名称来完成。


发送邮件

要发送一条信息,可以使用 Mail Facade 上的 to 方法。to 方法接收邮箱地址、用户实例或用户集合作为参数。如果传递的是对象或对象集合,在设置邮件收件人的时候邮件会自动使用它们的 email 和 name 属性,所以事先要确保这些属性在相应类上有效。指定好收件人以后,传递一个可邮寄类的实例到 send 方法:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Mail\OrderShipped;
use App\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class OrderController extends Controller
{
    /**
     * Ship the given order.
     *
     * @param  Request  $request
     * @param  int  $orderId
     * @return Response
     */
    public function ship(Request $request, $orderId)
    {
        $order = Order::findOrFail($orderId);

        // Ship order...

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

当然,发送邮件消息时并不仅限于指定单个收件人,你可以通过单个方法链调用设置tocc以及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));
}

通过指定邮件服务发送邮件

默认情况下,Laravel 使用 mail 配置文件中 default 指定的邮件服务配置发送邮件。你还可以在代码中使用 mailer 方法指定特定的邮件服务发送邮件:

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

邮件队列

邮件消息队列

由于发送邮件消息可能会大幅度延长应用的响应时间,许多开发者选择将邮件发送放到队列中在后台发送,Laravel 中可以使用内置的统一队列 API 来实现这一功能。要将邮件消息推送到队列,可以在指定消息的接收者后使用 Mail Facade 上的 queue 方法:

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

该方法自动将邮件任务推送到队列以便在后台发送。当然,你需要在使用该特性前配置队列。

延迟消息队列

如果想要延迟队列中邮件消息的发送,可以使用 later 方法。later 方法的第一个参数接收一个 DateTime 实例来表示邮件发送时间:

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

推送到指定队列

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

$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
{
    //
}

渲染可邮寄类

有时候我们可能想要在不发送可邮寄类的情况下捕获其 HTML 内容,要实现这个功能,可以调用可邮寄类的 render 方法,该方法会将处理后的邮件内容以字符串方式返回:

$invoice = App\Invoice::find(1);
return (new App\Mail\InvoicePaid($invoice))->render();

在浏览器中预览邮件

设计邮件的模板时,如果可以像普通的 Blade 模板一样快速预览渲染后的邮件是很方便的。因此,Laravel 允许你从路由闭包或控制器中直接返回可邮寄类。当可邮寄类返回时,就会在浏览器渲染并展示,从而方便你快速预览而不必真的发送邮件后查看:

Route::get('/mailable', function () {
    $invoice = App\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.
     *
     * @return string
     */
    public function preferredLocale()
    {
        return $this->locale;
    }
}

实现这个接口后,Laravel 就会在发送可邮寄类和通知到该模型时自动使用首选本地化语言。因此,在调用的时候也就没有必要显式调用 locale 方法了:

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

邮件 & 本地开发

本地环境开发发送邮件的应用时,你可能不想要真的发送邮件到有效的电子邮件地址,而只是想要做下测试。为此,Laravel 提供了几种方式“取消”邮件的实际发送。

日志驱动

一种解决方案是在本地开发时使用 log 邮件驱动。该驱动将所有邮件信息写到日志文件中以备查看,想要了解更多关于每个环境的应用配置信息,查看配置文档

通用配置

Laravel 提供的另一种解决方案是为框架发送的所有邮件设置通用收件人,这样的话,所有应用生成的邮件将会被发送到指定地址,而不是实际发送邮件指定的地址。这可以通过在配置文件 config/mail.php 中设置 to 选项来实现:

'to' => [
    'address' => 'example@example.com',
    'name' => 'Example'
],

Mailtrap

最后,我们可以使用 Mailtrap 服务和 smtp 驱动发送邮件信息到“虚拟”邮箱,这种方法允许你在 Mailtrap 的消息查看器中查看最终的邮件。


事件

Laravel 会在发送邮件消息前触发两个事件,MessageSending 事件在消息发送前触发,MessageSent 事件在消息发送后触发。需要注意的是这两个事件是在邮件被发送前后触发,而不是推送到队列时。我们可以在 EventServiceProvider 中注册对应的事件监听器:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'Illuminate\Mail\Events\MessageSending' => [
        'App\Listeners\LogSendingMessage',
    ],
    'Illuminate\Mail\Events\MessageSent' => [
        'App\Listeners\LogSentMessage',
    ],
];

查看笔记

扫码一下
查看教程更方便