Mail

简介

Laravel基于热门的SwiftMailer 函数库提供了一套干净、简洁的API,可以为 SMTP、Mailgun、Postmark、Amazon SES 和 sendmail 提供驱动,让你可以快速从本地或云端服务自由地发送邮件:

配置

Laravel的 email 服务可以通过 mail 配置文件进行配置。 这个文件中配置地每个邮件程序都可能有自己地配置项,甚至是独有的传输方式,这将允许你的应用程序使用不同的邮件服务来发送特定的邮件。例如,你的应用程序可能使用 Postmark 发送事务性邮件,而使用 Amazon SES 发送批量邮件。

驱动前提

基于 API 的驱动,比如 Mailgun 和 Postmark 通常比 SMTP 服务器更简单快速。如果可以的话, 你应该尽可能使用这些驱动。所有的 API 驱动都需要 Guzzle HTTP 函数库,这个函数库可以通过 Composer 包管理安装:

  1. composer require guzzlehttp/guzzle

Mailgun 驱动

要使用 Mailgun 驱动,首先必须安装 Guzzle,之后将 config/mail.php 配置文件中的 driver 选项设置为 mailgun。接下来,确认 config/services.php 配置文件包含以下选项:

  1. 'mailgun' => [
  2. 'domain' => 'your-mailgun-domain',
  3. 'secret' => 'your-mailgun-key',
  4. ],

如果你不使用 "US" Mailgun 区域,你可以在 services 配置文件中定义自己的区域终端地址:

  1. 'mailgun' => [
  2. 'domain' => 'your-mailgun-domain',
  3. 'secret' => 'your-mailgun-key',
  4. 'endpoint' => 'api.eu.mailgun.net',
  5. ],

Postmark 驱动

要使用 Postmark 驱动, 需要先通过 Composer 安装 Postmark 的 SwiftMailer 函数库:

  1. composer require wildbit/swiftmailer-postmark

然后,安装 Guzzle 并为 postmark 设置 config/mail.php 配置文件中的 driver 选项。最后, 确认你的 config/services.php 配置文件包含以下选项:

  1. 'postmark' => [
  2. 'token' => 'your-postmark-token',
  3. ],

SES 驱动

要使用 Amazon SES 驱动,你必须先安装 PHP 的 Amazon AWS SDK 。你可以在 composer.json 文件的 require 段落加入下面这一行并运行 composer update 命令:

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

然后,将 config/mail.php 配置文件的 driver 选项设置成 ses 并确认你的 config/services.php 配置文件包含以下选项:

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

如果你在执行 SES SendRawEmail 请求的时候需要包含 附加选项, 你可以在 ses 配置中定义一个 options 数组:

  1. 'ses' => [
  2. 'key' => 'your-ses-key',
  3. 'secret' => 'your-ses-secret',
  4. 'region' => 'ses-region', // e.g. us-east-1
  5. 'options' => [
  6. 'ConfigurationSetName' => 'MyConfigurationSet',
  7. 'Tags' => [
  8. [
  9. 'Name' => 'foo',
  10. 'Value' => 'bar',
  11. ],
  12. ],
  13. ],
  14. ],

生成可邮寄类

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

  1. php artisan make:mail OrderShipped

编写可邮寄类

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

配置发件人

使用 from 方法

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

  1. /**
  2. * Build the message.
  3. *
  4. * @return $this
  5. */
  6. public function build()
  7. {
  8. return $this->from('example@example.com')
  9. ->view('emails.orders.shipped');
  10. }

使用全局的 from 地址

不过,如果你的应用在所有邮件中都使用相同的发送地址,在每个生成的可邮寄类中都调用 from 方法就显得很累赘。取而代之地,你可以在配置文件 config/mail.php 中指定一个全局的发送地址, 如果在 mailable 类中未指定其他 from 地址,则将使用此地址:

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

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

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

配置视图

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

  1. /**
  2. * Build the message.
  3. *
  4. * @return $this
  5. */
  6. public function build()
  7. {
  8. return $this->view('emails.orders.shipped');
  9. }

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

纯文本邮件

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

  1. /**
  2. * 构建消息
  3. *
  4. * @return $this
  5. */
  6. public function build()
  7. {
  8. return $this->view('emails.orders.shipped')
  9. ->text('emails.orders.shipped_plain');
  10. }

视图数据

通过公共属性

通常,我们需要传递一些数据到渲染邮件的 HTML 视图以供使用。有两种方式将数据传递到视图,首先,您的 mailable 类中定义的任何公共属性将自动传递给视图。 因此,您可以将数据传递到 mailable 类的构造函数,并将该数据设置为类的公共属性:

  1. <?php
  2. namespace App\Mail;
  3. use App\Order;
  4. use Illuminate\Bus\Queueable;
  5. use Illuminate\Mail\Mailable;
  6. use Illuminate\Queue\SerializesModels;
  7. class OrderShipped extends Mailable
  8. {
  9. use Queueable, SerializesModels;
  10. /**
  11. * 订单实例
  12. *
  13. * @var Order
  14. */
  15. public $order;
  16. /**
  17. * 创建一个新的消息实例.
  18. *
  19. * @return void
  20. */
  21. public function __construct(Order $order)
  22. {
  23. $this->order = $order;
  24. }
  25. /**
  26. * 构建消息
  27. *
  28. * @return $this
  29. */
  30. public function build()
  31. {
  32. return $this->view('emails.orders.shipped');
  33. }
  34. }

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

  1. <div>
  2. Price: {{ $order->price }}
  3. </div>

通过 with 方法

如果你想要在数据发送到模板之前自定义邮件数据的格式,可以通过 with 方法手动传递数据到视图。一般情况下,你还是需要通过可邮寄类的构造器传递数据,不过,这次你需要设置数据为 protectedprivate 属性,这样,这些数据就不会在视图中自动生效。然后,当调用 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;

    /**
     * 订单实例
     *
     * @var Order
     */
    protected $order;

    /**
     * 创建一个新的消息实例.
     *
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    /**
     * 构建消息
     *
     * @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($pathToImage) }}">
</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 格式的 Mailables 类

Markdown 格式 mailable 消息允许你从预构建模板和 mailable 类中的邮件通知组件获益。由于消息使用 Markdown 书写,Laravel 能够渲染出美观的、响应式的 HTML 模板消息,还能自动生成文本副本。

生成 Markdown 格式的 Mailables 类

要生成一个适用 Markdown 模板的 mailable,可以使用带有 —markdown 选项的 make:mail Artisan 命令:

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

然后,在它的 build 方法中配置 mailable,调用 markdown 方法代替 view 方法。 markdown 方法接受 Markdown 模板名和一个可选的在模板中可用的数组:

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

编写 Markdown 消息

Markdown mailable 使用 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。颜色选项支持 primarysuccesserror。你可以随心所欲地向消息添加任意数量的按钮组件:

@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 邮件组件导出到自己的应用,用作自定义组件的模板。若要导出这些组件,使用带有 laravel-mail 资产标签的 vendor:publish Artisan 命令:

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 表示中。

如果想为 Laravel 的 Markdown 组件构建一个全新的主题,您可以在 html/themes 目录中新建一个CSS文件。 命名并保存 CSS 文件后,并更新 mail 配置文件的 theme 选项以匹配新主题的名称。

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

发送邮件

若要发送邮件,使用 Mail facadeto 方法。 to 方法接受 邮件地址、用户实例或用户集合。如果传递一个对象或者对象集合,mailer 在设置收件人时将自动使用它们的 emailname 属性,因此请确保对象的这些属性可用。一旦指定了收件人,就可以将 mailable 类实例传递给 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
{
    /**
     * 发送给定的订单。
     *
     * @param  Request  $request
     * @param  int  $orderId
     * @return Response
     */
    public function ship(Request $request, $orderId)
    {
        $order = Order::findOrFail($orderId);

        // 发送订单...

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

在发送消息时不止可以指定收件人。还可以通过链式调用「to」、「cc」、「bcc」一次性指定抄送和密送收件人:

use Illuminate\Support\Facades\Mail;

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

通过特定的 Mailer 发送邮件

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

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

渲染 Mailables

有时可能希望捕获 mailable 的 HTML 内容,而不发送它。可以调用 mailable 的 render 方法实现此目的。此方法返回 mailable 渲染计算后的字符串:

$invoice = App\Invoice::find(1);

return (new App\Mail\InvoicePaid($invoice))->render();

在浏览器中预览 Mailable

设计 mailable 模板时,像 Blade 模板一样在浏览器中预览和渲染 mailable 是很方便的。这种情况下,Laravel 允许你在路由闭包或控制其中直接返回任意 mailable。返回的 mailable将在浏览器中渲染和显示,你可以快速预览设计效果,而不需要将其发送到真实的邮件地址:

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

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

邮件队列

将邮件消息加入队列

由于发送邮件消息可能大幅度延长应用的响应时间,许多开发者选择将邮件消息加入队列放在后台发送。Laravel 使用内置的 统一队列 API 简化了这一工作。若要将邮件消息加入队列,可以在指定消息的接收者后,使用 Mail facade 的 queue 方法:

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

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

延迟消息队列

想要延迟发送队列化的邮件消息,可以使用 later 方法。later 方法的第一个参数的第一个参数是标示消息何时发送的 DateTime 实例:

$when = now()->addMinutes(10);

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

推送到指定队列

由于所有使用 make:mail 命令生成的 mailable 类都是用了 Illuminate\Bus\Queueable trait,因此你可以在任何 mailable 类实例上调用 onQueueonConnection 方法来指定消息的连接和队列名:

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

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

默认队列

如果一个 mailable 类终是要队列化,可以在此类上实现 ShouldQueue 契约。这样一来,即使你在发送时调用了 send 方法, mailable 也将被序列化:

use Illuminate\Contracts\Queue\ShouldQueue;

class OrderShipped extends Mailable implements ShouldQueue
{
    //
}

本地化 Mailables

Laravel 允许你使用有别于当前语言的区域设置发送 mailable,即使被加入到队列中也保留该区域设置。

为达到此目的, Mail facade 提供了 locale 方法设置目标语言。应用在格式化 mailable 是将切换到该区域设置,并在格式化完成后恢复到原来的区域设置:

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

用户首选区域设置

有时候,应用存储每个用户的首选区域设置。通过在一个或多个模型上实现 HasLocalePreference 契约,可以通知 Laravel 再发送邮件时使用预存的区域设置:

use Illuminate\Contracts\Translation\HasLocalePreference;

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

一旦实现了此接口,Laravel 在向此模型发送 mailable 和通知时,将自动使用首选区域设置。因此在使用此接口时不需要调用 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 中注册此事件的侦听器:

/**
 * 为应用映射事件侦听器。
 *
 * @var array
 */
protected $listen = [
    'Illuminate\Mail\Events\MessageSending' => [
        'App\Listeners\LogSendingMessage',
    ],
    'Illuminate\Mail\Events\MessageSent' => [
        'App\Listeners\LogSentMessage',
    ],
];

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

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

Laravel China 社区:https://learnku.com/docs/laravel/7.x/mail/7488