邮件
简介
Laravel 为流行的 SwiftMailer 库提供了简洁易用的 API,支持 SMTP,Mailgun,SparkPost,Amazon SES,PHP 的 mail
函数和 sendmail
驱动,让您可以快速通过本地服务或者所选的云服务发送邮件。
驱动前提
基于 API 的驱动(例如 Mailgun 和 SparkPost)通常比 SMTP 服务器更简单更快速。如果可能,应该使用这些驱动之一。所有 API 驱动都需要 Guzzle HTTP 库,可以通过 Composer 包管理器进行安装:
composer require guzzlehttp/guzzle
Mailgun 驱动
使用 Mailgun 驱动,首先安装 Guzzle,然后在 config/mail.php
配置文件中将 driver
选项设置为 mailgun
。接下来,确保 config/services.php
配置文件包含如下选项:
'mailgun' => [
'domain' => 'your-mailgun-domain',
'secret' => 'your-mailgun-key',
],
SparkPost 驱动
使用 SparkPost 驱动,首先安装 Guzzle,然后在 config/mail.php
配置文件中将 driver
选项设置为 sparkpost
。接下来,确保 config/services.php
配置文件包含如下选项:
'sparkpost' => [
'secret' => 'your-sparkpost-key',
],
如有需要,还可以配置使用的 API 端点:
'sparkpost' => [
'secret' => 'your-sparkpost-key',
'options' => [
'endpoint' => 'https://api.eu.sparkpost.com/api/v1/transmissions',
],
],
SES 驱动
使用 Amazon SES 驱动,首先要安装 PHP 专用的 Amazon AWS SDK。可以将如下一行添加到 composer.json
文件的 require
里面,然后运行 composer update
命令安装此库:
"aws/aws-sdk-php": "~3.0"
接下来,在 config/mail.php
配置文件中将 driver
选项设置为 ses
,并确保 config/services.php
配置文件包含如下选项:
'ses' => [
'key' => 'your-ses-key',
'secret' => 'your-ses-secret',
'region' => 'ses-region', // 例如 us-east-1
],
如果要在发起 SES SendRawEmail
请求时引入 其它选项,可以在 ses
配置项中定义一个 options
数组:
'ses' => [
'key' => 'your-ses-key',
'secret' => 'your-ses-secret',
'region' => 'ses-region', // 例如 us-east-1
'options' => [
'ConfigurationSetName' => 'MyConfigurationSet',
'Tags' => [
[
'Name' => 'foo',
'Value' => 'bar',
],
],
],
],
生成邮件
在 Laravel 中,应用发送的每种类型的邮件都表示为「Mailable」类。这些类都存储在 app/Mail
目录中。如果在应用中没有看到此目录也不用担心,因为它会在使用 make:mail
命令创建第一个邮件类时自动生成:
php artisan make:mail OrderShipped
编写邮件
所有邮件类的配置都在 build
方法中完成。在此方法中,您可以调用各种方法,如 from
,subject
,view
和 attach
来配置邮件内容和发件人。
配置发件人
使用 from
方法
首先,我们来研究下如何配置邮件的发件人。或者,换句话说,邮件是由谁发出的。有两种方法配置发件人。第一种,在邮件类的 build
方法中使用 from
方法:
/**
* 构建邮件信息
*
* @return $this
*/
public function build()
{
return $this->from('example@example.com')
->view('emails.orders.shipped');
}
使用全局 from
地址
然而,如果应用使用同一地址来发送所有邮件,在每个生成的邮件类中调用 from
方法会很麻烦。因此,可以在 config/mail.php
配置文件中指定一个全局的「from」地址。如果在邮件类中没有指定其它的「from」地址,就会使用此全局地址:
'from' => ['address' => 'example@example.com', 'name' => 'App Name'],
此外,还可以在 config/mail.php
配置文件中定义一个全局的 「reply_to」地址:
'reply_to' => ['address' => 'example@example.com', 'name' => 'App Name'],
配置视图
在邮件类的 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');
}
视图数据
通过公有属性
通常情况下,会传递一些数据到视图,以便在渲染邮件 HTML 时使用。有两种方式可以让视图获取数据。第一种,任何定义在邮件类中的公有属性会自动在视图中可用。因此,可以将数据传递到邮件类的构造函数中,然后赋值给类中定义的公有属性:
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
*/
public $order;
/**
* 创建新的信息实例
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* 构建信息
*
* @return $this
*/
public function build()
{
return $this->view('emails.orders.shipped');
}
}
数据赋值给公有属性后,它会自动在视图中可用,因此可以在 Blade 模板中像访问任何其它数据一样访问它:
<div>
Price: {{ $order->price }}
</div>
通过 with
方法
如果要在将数据发送给模板之前自定义邮件数据格式,可以通过 with
方法手动将数据传递到视图。通常情况下,仍会通过邮件类的构造函数传递数据;但是,应该将数据赋值给受保护或私有属性,这样数据就不会自动在视图中可用。然后,调用 with
方法时,传递一个希望在视图中可用的数组数据:
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
方法接收文件的完整路径作为其第一个参数:
/**
* 构建信息
*
* @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',
]);
}
原始数据附件
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($pathToFile) }}">
</body>
$message
变量在 Markdown 信息中不可使用。
嵌入原始数据附件
如果有要嵌入到邮件模板中的原始字节数据,可以在 $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 邮件
Markdown 邮件信息允许您利用邮件中预先构建的模板和邮件通知组件。由于信息是用 Markdown 编写的,因此 Laravel 能够将信息渲染为漂亮的、响应式 HTML 模板,还会自动生成纯文本副本。
生成 Markdown 邮件
要生成一个邮件和对应的 Markdown 模板,可以在使用 Artisan 命令 make:mail
的 --markdown
选项:
php artisan make:mail OrderShipped --markdown=emails.orders.shipped
然后,在 build
方法中配置邮件时,调用 markdown
方法而不是 view
方法。markdown
方法接收 Markdown 模板名和一个可选的在模板中可用的数组数据:
/**
* 构建信息
*
* @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
和一个 markdown
目录,每个目录中包含每个组件相应的内容。html
目录中的组件用于生成 HTML 版本的邮件,markdown
目录对应的副本用于生成纯文本版本。可以随意自定义这些组件。
自定义 CSS
导出组件后,resources/views/vendor/mail/html/themes
目录会包含一个 default.css
文件。可以自定义此文件中的 CSS,样式会自动内嵌到 Markdown 邮件信息的 HTML 内容中。
如果要为 Markdown 组件构建一个全新的主题,可以在
html/themes
目录中编写一个新的 CSS 文件并更改theme
选项。
发送邮件
发送邮件,可以使用 Mail
Facade 的 to
方法。to
方法接收邮件地址,用户实例或用户集合。如果传递一个对象或对象的集合,在设置邮件收件人时,会自动使用他们的 email
和 name
属性,因此确保这些属性在对象中可用。指定收件人后,可以传递一个邮件类的实例给 send
方法:
namespace App\Http\Controllers;
use App\Order;
use App\Mail\OrderShipped;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Http\Controllers\Controller;
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));
}
}
当然,不仅限于在发送邮件时指定收件人。可以调用单个链式方法指定「收件人」,「抄送」和「私密抄送」:
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));
渲染邮件
有时可能希望在不发送邮件的情况下显示邮件的 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 内置的 统一队列 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
命令生成的邮件类都使用了 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
Contract。现在,即使只在发送邮件时使用 send
方法,邮件也会通过队列执行,因为它实现了此 Contract:
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShipped extends Mailable implements ShouldQueue
{
//
}
本地化邮件
Laravel 允许发送邮件时使用当前语言以外的语言,甚至邮件在队列中也会记住此语言。
为此,Illuminate\Mail\Mailable
类提供了一个 locale
方法用来设置要使用的语言。在格式化邮件时应用会自动切换到该语言,格式化完成后恢复为之前的语言:
Mail::to($request->user())->send(
(new OrderShipped($order))->locale('es')
);
用户首选语言
有时,应用存储了每个用户的首选语言。通过在一个模型或更多模型上实现 HasLocalePreference
Contract,可以指示 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
最后,可以使用像 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',
],
];