邮件发送 简介 Laravel 基于热门的 SwiftMailer 函数库提供了一套干净、简洁的 API ,可以为 SMTP、Mailgun、Postmark、Amazon SES 和 sendmail 提供驱动,让你可以快速从本地或云端服务自由地发送邮件。 配置 Laravel 的 email 服务可以通过 mail 配置文件进行配置。 这个文件中配置地每个邮件程序都可能有自己地配置项,甚至是独有的「传输方式」,这将允许你的应用程序使用不同的邮件服务来发送特定的邮件。例如,你的应用程序可能使用 Postmark 发送事务性邮件,而使用 Amazon SES 发送批量邮件。 驱动前提 基于 API 的驱动,比如 Mailgun 和 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', ], ``` 如果你不使用 “US” 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 并为 postmark 设置 config/mail.php 配置文件中的 default 选项。最后,确认你的 config/services.php 配置文件包含以下选项: ``` 'postmark' => [ 'token' => 'your-postmark-token', ], ``` SES 驱动 要使用 Amazon SES 驱动,你必须先安装 PHP 的 Amazon AWS 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', ], ], ], ], ``` 生成 Mailables(可邮寄类) 在 Laravel 中,应用发送的每种邮件都被表示为 mailable 类。这些类存储于 app/Mail 目录中。如果您的应用中没有该目录,别慌,当您使用 make:mail 命令生成您的首个 mailable 类时,应用将会自动创建它: ``` php artisan make:mail OrderShipped ``` 编写 Mailables 所有的 mailable 类的配置都在 build 方法中完成。您可以通过调用诸如 from , subject , view 和 attach 这样的各种各样的方法来配置邮件的内容及其发送。 配置发件人 使用 from 方法 首先,让我们浏览一下邮件的发件人的配置。或者,换句话说,邮件来自谁。有两种方法配置发件人。第一种,您可以在您的 mailable 类的 build 方法中使用 from 方法: ``` /** * 编译消息。 * * @return $this */ public function build() { return $this->from('example@example.com') ->view('emails.orders.shipped'); } ``` 使用全局的 from 地址 当然,如果您的应用在任何邮件中使用的「发件人」地址都一致的话,在您生成的每一个 mailable 类中调用 from 方法可能会很麻烦。因此,您可以在您的 config/mail.php 文件中指定一个全局的「发件人」地址。当某个 mailable 类没有指定「发件人」时,它将使用该全局「发件人」: ``` 'from' => ['address' => 'example@example.com', 'name' => 'App Name'], ``` 此外,您亦可在您的 config/mail.php 配置文件中定义一个全局的「回复」地址: ``` 'reply_to' => ['address' => 'example@example.com', 'name' => 'App Name'], ``` 配置邮件视图 您可以在 mailable 类的 build 方法中使用 view 方法来指定在渲染邮件内容时要使用的模板。由于每封邮件通常使用 Blade 模板 来渲染其内容,因此您可以在构建您的邮件 HTML 内容时使用 Blade 模板引擎提供的所有功能及享受其带来的便利性: ``` /** * 构建邮件消息。 * * @return $this */ public function build() { return $this->view('emails.orders.shipped'); } ``` 技巧:您可以创建一个 resources/views/emails 目录来存放您的所有邮件模板;当然,您也可以将其置于 resources/views 目录下的任何位置。 纯文本邮件 您可以使用 text 方法来定义一个纯文本格式的邮件。和 view 方法一样,text 方法接受一个模板名,模板名指定了在渲染邮件内容时您想使用的模板。您既可以定义纯文本消息亦可定义 HTML 消息: ``` /** * 构建邮件消息。 * * @return $this */ public function build() { return $this->view('emails.orders.shipped') ->text('emails.orders.shipped_plain'); } ``` 视图数据 通过 Public 属性 通常情况下,您可能想要在渲染邮件的 HTML 内容时传递一些数据到视图中。有两种方法传递数据到时视图中。第一种,您在 mailable 类中定义的所有 public 的属性都将自动传递到视图中。因此,举个例子,您可以将数据传递到您的 mailable 类的构造函数中,并将其设置为类的 public 属性: ``` <?php namespace App\Mail; use App\Models\Order; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class OrderShipped extends Mailable { use Queueable, SerializesModels; /** * 订单实例。 * * @var Order */ public $order; /** * 创建一个消息实例。 * * @param \App\Models\Order $order * @return void */ public function __construct(Order $order) { $this->order = $order; } /** * 构造消息。 * * @return $this */ public function build() { return $this->view('emails.orders.shipped'); } } ``` 当数据被设置成为 public 属性之后,它将被自动传递到您的视图中,因此您可以像您在 Blade 模板中那样访问它们: ``` <div> Price: {{ $order->price }} </div> ``` 通过 with 方法: 如果您想要在邮件数据发送到模板前自定义它们的格式,您可以使用 with 方法来手动传递数据到视图中。一般情况下,您还是需要通过 mailable 类的构造函数来传递数据;不过,您应该将它们定义为 protected 或 private 以防止它们被自动传递到视图中。然后,在您调用 with 方法的时候,您可以以数组的形式传递您想要传递给模板的数据: ``` <?php namespace App\Mail; use App\Models\Order; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class OrderShipped extends Mailable { use Queueable, SerializesModels; /** * 订单实例。 * * @var \App\Models\Order */ protected $order; /** * 创建一个消息实例。 * * @param \App\Models\Order $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 方法接受文件的绝对路径作为它的第一个参数: ``` /** * 构建消息 * * @return $this */ public function build() { return $this->view('emails.orders.shipped') ->attach('/path/to/file'); } ``` 附加文件到消息时,你也可以传递 数组 给 attach 方法作为第二个参数,以指定显示名称和 / 或是 MIME 类型: ``` /** * 构建消息 * * @return $this */ public function build() { return $this->view('emails.orders.shipped') ->attach('/path/to/file', [ 'as' => 'name.pdf', 'mime' => 'application/pdf', ]); } ``` 从磁盘中添加附件 如果您已在 文件存储 上存储了一个文件,则可以使用 attachFromStorage 方法将其附加到电子邮件中: ``` /** * 构建消息 * * @return $this */ public function build() { return $this->view('emails.orders.shipped') ->attachFromStorage('/path/to/file'); } ``` 如有必要,您可以使用 attachFromStorage 方法的第二个和第三个参数指定文件的附件名称和其他选项: ``` /** * 构建消息 * * @return $this */ public function build() { return $this->view('emails.orders.shipped') ->attachFromStorage('/path/to/file', 'name.pdf', [ 'mime' => 'application/pdf' ]); } ``` 如果需要指定默认磁盘以外的存储磁盘,可以使用 attachFromStorageDisk 方法: ``` /** * 构建消息 * * @return $this */ public function build() { return $this->view('emails.orders.shipped') ->attachFromStorageDisk('s3', '/path/to/file'); } ``` 原始数据附件 attachData 可以使用字节数据作为附件。例如,你可以使用这个方法将内存中生成而没有保存到磁盘中的 PDF 附加到邮件中。attachData 方法第一个参数接收原始字节数据,第二个参数为文件名,第三个参数接受一个数组以指定其他参数: ``` /** * 构建消息 * * @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 消息将作为该回调的参数。借此机会,你可以在发消息前对其进行定制。 ``` /** * 构建消息 * * @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 格式的 Mailable 类 您可以在执行 make:mail Artisan 命令时使用 --markdown 选项来生成一个使用 Markdown 格式的模板的 mailable 类: ``` php artisan make:mail OrderShipped --markdown=emails.orders.shipped ``` 然后,在它的 build 方法中配置 mailable 类时,请使用 markdown 方法来代替 view 方法。markdown 方法接受 Markdown 模板的名称和想要传递给模板的可选的数组形式的数据: ``` /** * 构造邮件消息。 * * @return $this */ public function build() { return $this->from('example@example.com') ->markdown('emails.orders.shipped'); } ``` 编写 Markdown 消息 Markdown mailable 类整合了 Markdown 语法和 Blade 组件,让您能够非常方便的使用 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 一个是可选的 颜色 。可用的颜色包括 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 邮件组件导出到自己的应用,用作自定义组件的模板。若要导出这些组件,使用带有 laravel-mail 资产标签的 vendor:publish Artisan 命令: ``` 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 表示中。 如果想为 Laravel 的 Markdown 组件构建一个全新的主题,您可以在 html/themes 目录中新建一个 CSS 文件。 命名并保存 CSS 文件后,并更新 mail 配置文件的 theme 选项以匹配新主题的名称。 要为单个 mailable 自定义主题,可以将 mailable 类的 $theme 属性设置为发送 mailable 时应使用的主题名称。 发送邮件 若要发送邮件,使用 Mail facade 的 to 方法。 to 方法接受 邮件地址、用户实例或用户集合。如果传递一个对象或者对象集合,mailer 在设置收件人时将自动使用它们的 email 和 name 属性,因此请确保对象的这些属性可用。一旦指定了收件人,就可以将 mailable 类实例传递给 send 方法: ``` <?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Mail\OrderShipped; use App\Models\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)); ``` 遍历收件人列表 有时,你需要通过遍历一个收件人 / 邮件地址数组的方式,给一系列收件人发送邮件。因为 to 方法会给 mailable 列表中的收件人追加邮件地址,你应该为每个收件人重建 mailable 实例。 ``` foreach (['taylor@example.com', 'dries@example.com'] as $recipient) { Mail::to($recipient)->send(new OrderShipped($order)); } ``` 通过特定的 Mailer 发送邮件 默认情况下,Laravel 将使用你的 mail 配置文件中配置为 default 邮件程序。 但是,你可以使用 mailer 方法通过特定的邮件程序配置发送: ``` Mail::mailer('postmark') ->to($request->user()) ->send(new OrderShipped($order)); ``` 邮件队列 将邮件消息加入队列 由于发送邮件消息可能大幅度延长应用的响应时间,许多开发者选择将邮件消息加入队列放在后台发送。Laravel 使用内置的 统一队列 API 简化了这一工作。若要将邮件消息加入队列,可以在指定消息的接收者后,使用 Mail 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 类实例上调用 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\Models\Invoice::find(1); return (new App\Mail\InvoicePaid($invoice))->render(); ``` 在浏览器中预览邮件 设计邮件模板时,可以方便地在浏览器中预览邮件,就像典型的 Blade 模板一样。因此, Laravel 允许您直接从路由闭包或控制器返回任何邮件类。当邮件返回时,它将渲染并显示在浏览器中,允许您快速预览其设计,而无需将其发送到实际的电子邮件地址 ``` Route::get('mailable', function () { $invoice = App\Models\Invoice::find(1); return new App\Mail\InvoicePaid($invoice); }); ``` 邮件类的本地化 Laravel 允许您以当前语言以外的语言发送邮件,如果是队列邮件,甚至会记住这个区域设置。 为了实现这一点,Mail Facade 提供了一个 locale 方法来设置所需的语言。在格式化邮件时,应用程序将更改为该区域设置,然后在格式化完成后恢复到以前的区域设置 ``` 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 将在向模型发送邮件和通知时自动使用该语言环境。因此,使用此接口时不需要调用 locale 方法: ``` Mail::to($request->user())->send(new OrderShipped($order)); ``` 本地开发 当你正在开发一个邮件的应用程序时,您可能不想实际地向真实邮件地址发送邮件。Laravel 提供了几种在本地开发过程中「禁用」实际发送电子邮件的方法 日志驱动 log 邮件驱动程序不发送邮件,而是将所有邮件消息写入日志文件用来校验。有关为每个环境配置应用程序的更多信息,请参阅配置文档 统一收件人 Laravel 提供的另一个解决方案是为框架发送的所有邮件设置一个统一的收件人。这样应用程序生成的所有电子邮件都将发送到特定的地址,而不是发送消息时指定的地址。你可以在 config/mail.php 配置文件中的 to 选项来启用: ``` 'to' => [ 'address' => 'example@example.com', 'name' => 'Example' ], ``` Mailtrap(虚拟的 smtp 测试服务) 最后,您可以使用像 Mailtrap 这样的服务和 smtp 驱动发送邮件消息到「dummy」邮箱中,这样做,您便可以在真实的邮箱客户端中查看您的邮件。此举的好处是允许您在 Mailtrap 的消息查看器中实际查看最终的邮件。 事件 在发送邮件消息的时候,Laravel 会启动两个事件。MessageSending 事件在发送消息前触发,MessageSent 事件在消息发送完成后触发。记住,这些事件都是在邮件被 发送 时触发,而不是在队列化的时候。您可以在 EventServiceProvider 中注册一个事件***: ``` /** * 为应用映射事件***。 * * @var array */ protected $listen = [ 'Illuminate\Mail\Events\MessageSending' => [ 'App\Listeners\LogSendingMessage', ], 'Illuminate\Mail\Events\MessageSent' => [ 'App\Listeners\LogSentMessage', ], ]; ```