郵件
簡介
傳送電子郵件不必複雜。Laravel 提供了一個簡潔、簡單的電子郵件 API,它由流行的 Symfony Mailer 元件驅動。Laravel 和 Symfony Mailer 提供了透過 SMTP、Mailgun、Postmark、Resend、Amazon SES 和 sendmail 傳送郵件的驅動程式,使您能夠快速開始透過您選擇的本地或雲服務傳送郵件。
配置
Laravel 的電子郵件服務可以透過應用程式的 config/mail.php 配置檔案進行配置。在此檔案中配置的每個郵件程式都可以擁有其獨特的配置,甚至擁有其獨特的“傳輸方式”,從而允許您的應用程式使用不同的電子郵件服務傳送特定的電子郵件訊息。例如,您的應用程式可以使用 Postmark 傳送事務性郵件,同時使用 Amazon SES 傳送批次郵件。
在您的 mail 配置檔案中,您會找到一個 mailers 配置陣列。此陣列包含 Laravel 支援的每個主要郵件驅動程式/傳輸方式的示例配置條目,而 default 配置值決定了當應用程式需要傳送電子郵件訊息時預設使用的郵件程式。
驅動程式 / 傳輸方式先決條件
基於 API 的驅動程式(如 Mailgun、Postmark 和 Resend)通常比透過 SMTP 伺服器傳送郵件更簡單、更快捷。在可能的情況下,我們建議您使用這些驅動程式之一。
Mailgun 驅動程式
要使用 Mailgun 驅動程式,請透過 Composer 安裝 Symfony 的 Mailgun Mailer 傳輸器
1composer require symfony/mailgun-mailer symfony/http-client
接下來,您需要在應用程式的 config/mail.php 配置檔案中進行兩處更改。首先,將預設郵件程式設定為 mailgun
1'default' => env('MAIL_MAILER', 'mailgun'),
其次,將以下配置陣列新增到您的 mailers 陣列中
1'mailgun' => [2 'transport' => 'mailgun',3 // 'client' => [4 // 'timeout' => 5,5 // ],6],
配置好應用程式的預設郵件程式後,將以下選項新增到您的 config/services.php 配置檔案中
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),5 'scheme' => 'https',6],
如果您使用的不是美國 Mailgun 區域,您可以在 services 配置檔案中定義您所在區域的端點
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),5 'scheme' => 'https',6],
Postmark 驅動程式
要使用 Postmark 驅動程式,請透過 Composer 安裝 Symfony 的 Postmark Mailer 傳輸器
1composer require symfony/postmark-mailer symfony/http-client
接下來,將應用程式 config/mail.php 配置檔案中的 default 選項設定為 postmark。配置好應用程式的預設郵件程式後,確保您的 config/services.php 配置檔案包含以下選項
1'postmark' => [2 'key' => env('POSTMARK_API_KEY'),3],
如果您想指定特定郵件程式應使用的 Postmark 訊息流,可以將 message_stream_id 配置選項新增到郵件程式的配置陣列中。此配置陣列位於應用程式的 config/mail.php 配置檔案中
1'postmark' => [2 'transport' => 'postmark',3 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),4 // 'client' => [5 // 'timeout' => 5,6 // ],7],
透過這種方式,您還可以設定多個使用不同訊息流的 Postmark 郵件程式。
Resend 驅動程式
要使用 Resend 驅動程式,請透過 Composer 安裝 Resend 的 PHP SDK
1composer require resend/resend-php
接下來,將應用程式 config/mail.php 配置檔案中的 default 選項設定為 resend。配置好應用程式的預設郵件程式後,確保您的 config/services.php 配置檔案包含以下選項
1'resend' => [2 'key' => env('RESEND_API_KEY'),3],
SES 驅動程式
要使用 Amazon SES 驅動程式,您必須首先安裝 Amazon AWS SDK for PHP。您可以透過 Composer 包管理器安裝此庫
1composer require aws/aws-sdk-php
接下來,將 config/mail.php 配置檔案中的 default 選項設定為 ses,並確認您的 config/services.php 配置檔案包含以下選項
1'ses' => [2 'key' => env('AWS_ACCESS_KEY_ID'),3 'secret' => env('AWS_SECRET_ACCESS_KEY'),4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),5],
要透過會話令牌利用 AWS 臨時憑證,您可以嚮應用程式的 SES 配置中新增 token 鍵
1'ses' => [2 'key' => env('AWS_ACCESS_KEY_ID'),3 'secret' => env('AWS_SECRET_ACCESS_KEY'),4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),5 'token' => env('AWS_SESSION_TOKEN'),6],
要與 SES 的 訂閱管理功能 互動,您可以在郵件訊息的 headers 方法返回的陣列中返回 X-Ses-List-Management-Options 標頭
1/** 2 * Get the message headers. 3 */ 4public function headers(): Headers 5{ 6 return new Headers( 7 text: [ 8 'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic', 9 ],10 );11}
如果您想定義 Laravel 在傳送郵件時應傳遞給 AWS SDK 的 SendEmail 方法的 附加選項,您可以在 ses 配置中定義一個 options 陣列
1'ses' => [ 2 'key' => env('AWS_ACCESS_KEY_ID'), 3 'secret' => env('AWS_SECRET_ACCESS_KEY'), 4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 5 'options' => [ 6 'ConfigurationSetName' => 'MyConfigurationSet', 7 'EmailTags' => [ 8 ['Name' => 'foo', 'Value' => 'bar'], 9 ],10 ],11],
故障轉移 (Failover) 配置
有時,您為傳送應用程式郵件而配置的外部服務可能會宕機。在這種情況下,定義一個或多個備用郵件傳送配置會很有用,以備主要傳送驅動程式出現故障時使用。
為此,您應該在應用程式的 mail 配置檔案中定義一個使用 failover 傳輸器的郵件程式。應用程式 failover 郵件程式的配置陣列應包含一個 mailers 陣列,該陣列引用了選擇傳送郵件時應遵循的順序
1'mailers' => [ 2 'failover' => [ 3 'transport' => 'failover', 4 'mailers' => [ 5 'postmark', 6 'mailgun', 7 'sendmail', 8 ], 9 'retry_after' => 60,10 ],11 12 // ...13],
配置好使用 failover 傳輸器的郵件程式後,您需要在應用程式的 .env 檔案中將故障轉移郵件程式設定為預設郵件程式,以便使用該功能
1MAIL_MAILER=failover
輪詢 (Round Robin) 配置
roundrobin 傳輸器允許您在多個郵件程式之間分配郵件負載。首先,在應用程式的 mail 配置檔案中定義一個使用 roundrobin 傳輸器的郵件程式。roundrobin 郵件程式的配置陣列應包含一個 mailers 陣列,引用了應參與傳送的郵件程式
1'mailers' => [ 2 'roundrobin' => [ 3 'transport' => 'roundrobin', 4 'mailers' => [ 5 'ses', 6 'postmark', 7 ], 8 'retry_after' => 60, 9 ],10 11 // ...12],
定義好輪詢郵件程式後,您應透過將其名稱設定為應用程式 mail 配置檔案中 default 配置鍵的值,將其設定為應用程式使用的預設郵件程式
1'default' => env('MAIL_MAILER', 'roundrobin'),
輪詢傳輸器從已配置的郵件程式列表中隨機選擇一個,然後為隨後的每封郵件切換到下一個可用的郵件程式。與有助於實現 高可用性 的 failover 傳輸器不同,roundrobin 傳輸器提供的是 負載均衡。
生成郵件類 (Mailables)
在構建 Laravel 應用程式時,應用程式傳送的每種型別的電子郵件都表示為一個“郵件類”(Mailable)。這些類儲存在 app/Mail 目錄中。如果您在應用程式中沒有看到此目錄,請不必擔心,因為當您第一次使用 make:mail Artisan 命令建立郵件類時,它會自動為您生成
1php artisan make:mail OrderShipped
編寫郵件類
生成郵件類後,開啟它以探索其內容。郵件類的配置透過多個方法完成,包括 envelope、content 和 attachments 方法。
envelope 方法返回一個 Illuminate\Mail\Mailables\Envelope 物件,該物件定義了郵件的主題,有時還包括郵件的收件人。content 方法返回一個 Illuminate\Mail\Mailables\Content 物件,該物件定義了將用於生成郵件內容的 Blade 模板。
配置發件人
使用信封 (Envelope)
首先,讓我們探索如何配置電子郵件的發件人。或者換句話說,郵件是誰傳送的。有兩種方法可以配置發件人。首先,您可以在訊息的信封中指定“from”地址
1use Illuminate\Mail\Mailables\Address; 2use Illuminate\Mail\Mailables\Envelope; 3 4/** 5 * Get the message envelope. 6 */ 7public function envelope(): Envelope 8{ 9 return new Envelope(11 subject: 'Order Shipped',12 );13}
如果需要,您也可以指定一個 replyTo 地址
1return new Envelope(3 replyTo: [5 ],6 subject: 'Order Shipped',7);
使用全域性 from 地址
然而,如果您的應用程式的所有電子郵件都使用相同的“from”地址,那麼將它新增到您生成的每個郵件類中可能會很麻煩。相反,您可以在 config/mail.php 配置檔案中指定一個全域性“from”地址。如果郵件類中未指定其他“from”地址,則將使用此地址
1'from' => [3 'name' => env('MAIL_FROM_NAME', 'Example'),4],
此外,您還可以在 config/mail.php 配置檔案中定義一個全域性“reply_to”地址
1'reply_to' => [3 'name' => 'App Name',4],
配置檢視
在郵件類的 content 方法中,您可以定義 view,即渲染電子郵件內容時應使用的模板。由於每封電子郵件通常使用 Blade 模板 來渲染其內容,因此在構建電子郵件的 HTML 時,您可以充分利用 Blade 模板引擎的強大功能和便利性
1/**2 * Get the message content definition.3 */4public function content(): Content5{6 return new Content(7 view: 'mail.orders.shipped',8 );9}
您可能希望建立一個 resources/views/mail 目錄來存放所有的電子郵件模板;不過,您可以自由地將它們放置在 resources/views 目錄中的任何位置。
純文字電子郵件
如果您想定義電子郵件的純文字版本,可以在建立訊息的 Content 定義時指定純文字模板。與 view 引數一樣,text 引數應為用於渲染電子郵件內容的模板名稱。您可以自由地定義郵件的 HTML 和純文字版本
1/** 2 * Get the message content definition. 3 */ 4public function content(): Content 5{ 6 return new Content( 7 view: 'mail.orders.shipped', 8 text: 'mail.orders.shipped-text' 9 );10}
為了清晰起見,html 引數可以用作 view 引數的別名
1return new Content(2 html: 'mail.orders.shipped',3 text: 'mail.orders.shipped-text'4);
檢視資料
透過公共屬性
通常,您需要向檢視傳遞一些資料,以便在渲染電子郵件 HTML 時使用。有兩種方法可以讓檢視獲取資料。首先,郵件類中定義的任何公共屬性都將自動提供給檢視。因此,例如,您可以將資料傳入郵件類的建構函式,並將其設定為類中定義的公共屬性
1<?php 2 3namespace App\Mail; 4 5use App\Models\Order; 6use Illuminate\Bus\Queueable; 7use Illuminate\Mail\Mailable; 8use Illuminate\Mail\Mailables\Content; 9use Illuminate\Queue\SerializesModels;10 11class OrderShipped extends Mailable12{13 use Queueable, SerializesModels;14 15 /**16 * Create a new message instance.17 */18 public function __construct(19 public Order $order,20 ) {}21 22 /**23 * Get the message content definition.24 */25 public function content(): Content26 {27 return new Content(28 view: 'mail.orders.shipped',29 );30 }31}
一旦資料被設定為公共屬性,它就會自動在檢視中可用,因此您可以像訪問 Blade 模板中的任何其他資料一樣訪問它
1<div>2 Price: {{ $order->price }}3</div>
透過 with 引數
如果您想在傳送資料到模板之前自定義電子郵件資料的格式,可以透過 Content 定義的 with 引數手動將資料傳遞給檢視。通常,您仍然會透過郵件類的建構函式傳遞資料;但是,您應該將這些資料設定為 protected 或 private 屬性,這樣資料就不會自動提供給模板
1<?php 2 3namespace App\Mail; 4 5use App\Models\Order; 6use Illuminate\Bus\Queueable; 7use Illuminate\Mail\Mailable; 8use Illuminate\Mail\Mailables\Content; 9use Illuminate\Queue\SerializesModels;10 11class OrderShipped extends Mailable12{13 use Queueable, SerializesModels;14 15 /**16 * Create a new message instance.17 */18 public function __construct(19 protected Order $order,20 ) {}21 22 /**23 * Get the message content definition.24 */25 public function content(): Content26 {27 return new Content(28 view: 'mail.orders.shipped',29 with: [30 'orderName' => $this->order->name,31 'orderPrice' => $this->order->price,32 ],33 );34 }35}
一旦資料透過 with 引數傳遞,它就會自動在檢視中可用,因此您可以像訪問 Blade 模板中的任何其他資料一樣訪問它
1<div>2 Price: {{ $orderPrice }}3</div>
附件
要將附件新增到電子郵件,您需要將附件新增到訊息 attachments 方法返回的陣列中。首先,您可以透過提供檔案路徑給 Attachment 類提供的 fromPath 方法來新增附件
1use Illuminate\Mail\Mailables\Attachment; 2 3/** 4 * Get the attachments for the message. 5 * 6 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 7 */ 8public function attachments(): array 9{10 return [11 Attachment::fromPath('/path/to/file'),12 ];13}
在向訊息新增附件時,您還可以使用 as 和 withMime 方法指定附件的顯示名稱和/或 MIME 型別
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromPath('/path/to/file')10 ->as('name.pdf')11 ->withMime('application/pdf'),12 ];13}
從磁碟新增檔案附件
如果您已將檔案儲存在某個 檔案系統磁碟 上,則可以使用 fromStorage 附件方法將其附加到電子郵件中
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromStorage('/path/to/file'),10 ];11}
當然,您也可以指定附件的名稱和 MIME 型別
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromStorage('/path/to/file')10 ->as('name.pdf')11 ->withMime('application/pdf'),12 ];13}
如果您需要指定除預設磁碟之外的儲存磁碟,可以使用 fromStorageDisk 方法
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromStorageDisk('s3', '/path/to/file')10 ->as('name.pdf')11 ->withMime('application/pdf'),12 ];13}
原始資料附件
fromData 附件方法可用於將原始位元組字串作為附件新增。例如,如果您在記憶體中生成了一個 PDF 並且想在不寫入磁碟的情況下將其附加到電子郵件中,則可以使用此方法。fromData 方法接受一個閉包,該閉包解析原始資料位元組以及應分配給附件的名稱
1/** 2 * Get the attachments for the message. 3 * 4 * @return array<int, \Illuminate\Mail\Mailables\Attachment> 5 */ 6public function attachments(): array 7{ 8 return [ 9 Attachment::fromData(fn () => $this->pdf, 'Report.pdf')10 ->withMime('application/pdf'),11 ];12}
內聯附件
在電子郵件中嵌入內聯影像通常很麻煩;不過,Laravel 提供了一種方便的方法將影像附加到電子郵件中。要嵌入內聯影像,請在電子郵件模板中使用 $message 變數上的 embed 方法。Laravel 會自動使 $message 變數在所有電子郵件模板中可用,因此您無需擔心手動傳遞它
1<body>2 Here is an image:3 4 <img src="{{ $message->embed($pathToImage) }}">5</body>
$message 變數在純文字訊息模板中不可用,因為純文字訊息不使用內聯附件。
嵌入原始資料附件
如果您已經擁有希望嵌入到電子郵件模板中的原始影像資料字串,則可以呼叫 $message 變數上的 embedData 方法。呼叫 embedData 方法時,您需要提供應分配給嵌入影像的檔名
1<body>2 Here is an image from raw data:3 4 <img src="{{ $message->embedData($data, 'example-image.jpg') }}">5</body>
可附件物件 (Attachable Objects)
雖然透過簡單的字串路徑向訊息新增附件通常就足夠了,但在許多情況下,應用程式中的可附加實體由類表示。例如,如果您的應用程式正在向訊息新增照片,則您的應用程式可能還有一個表示該照片的 Photo 模型。如果是這種情況,直接將 Photo 模型傳遞給 attach 方法不是很方便嗎?可附件物件允許您做到這一點。
首先,在可附加到訊息的物件上實現 Illuminate\Contracts\Mail\Attachable 介面。此介面規定您的類必須定義一個 toMailAttachment 方法,該方法返回一個 Illuminate\Mail\Attachment 例項
1<?php 2 3namespace App\Models; 4 5use Illuminate\Contracts\Mail\Attachable; 6use Illuminate\Database\Eloquent\Model; 7use Illuminate\Mail\Attachment; 8 9class Photo extends Model implements Attachable10{11 /**12 * Get the attachable representation of the model.13 */14 public function toMailAttachment(): Attachment15 {16 return Attachment::fromPath('/path/to/file');17 }18}
一旦定義了可附件物件,在構建電子郵件訊息時,就可以從 attachments 方法中返回該物件的例項
1/**2 * Get the attachments for the message.3 *4 * @return array<int, \Illuminate\Mail\Mailables\Attachment>5 */6public function attachments(): array7{8 return [$this->photo];9}
當然,附件資料可以儲存在 Amazon S3 等遠端檔案儲存服務上。因此,Laravel 也允許您從儲存在應用程式 檔案系統磁碟 之一上的資料生成附件例項
1// Create an attachment from a file on your default disk...2return Attachment::fromStorage($this->path);3 4// Create an attachment from a file on a specific disk...5return Attachment::fromStorageDisk('backblaze', $this->path);
此外,您還可以透過記憶體中的資料建立附件例項。為此,請向 fromData 方法提供一個閉包。該閉包應返回表示附件的原始資料
1return Attachment::fromData(fn () => $this->content, 'Photo Name');
Laravel 還提供了其他方法,您可以用來自定義附件。例如,您可以使用 as 和 withMime 方法自定義檔案的名稱和 MIME 型別
1return Attachment::fromPath('/path/to/file')2 ->as('Photo Name')3 ->withMime('image/jpeg');
郵件頭 (Headers)
有時您可能需要向發出的訊息新增額外的標頭。例如,您可能需要設定自定義的 Message-Id 或其他任意文字標頭。
為此,請在您的郵件類中定義一個 headers 方法。headers 方法應返回一個 Illuminate\Mail\Mailables\Headers 例項。此類接受 messageId、references 和 text 引數。當然,您只需提供特定訊息所需的引數即可
1use Illuminate\Mail\Mailables\Headers; 2 3/** 4 * Get the message headers. 5 */ 6public function headers(): Headers 7{ 8 return new Headers(11 text: [12 'X-Custom-Header' => 'Custom Value',13 ],14 );15}
標籤與元資料
一些第三方電子郵件提供商(如 Mailgun 和 Postmark)支援訊息“標籤”和“元資料”,可用於分組和跟蹤應用程式傳送的電子郵件。您可以透過 Envelope 定義向電子郵件訊息新增標籤和元資料
1use Illuminate\Mail\Mailables\Envelope; 2 3/** 4 * Get the message envelope. 5 * 6 * @return \Illuminate\Mail\Mailables\Envelope 7 */ 8public function envelope(): Envelope 9{10 return new Envelope(11 subject: 'Order Shipped',12 tags: ['shipment'],13 metadata: [14 'order_id' => $this->order->id,15 ],16 );17}
如果您的應用程式正在使用 Mailgun 驅動程式,您可以查閱 Mailgun 文件以獲取有關 標籤 和 元資料 的更多資訊。同樣,也可以查閱 Postmark 文件以獲取有關他們對 標籤 和 元資料 支援的更多資訊。
如果您的應用程式使用 Amazon SES 傳送電子郵件,則應使用 metadata 方法將 SES "標籤" 附加到訊息中。
自定義 Symfony 郵件訊息
Laravel 的郵件功能由 Symfony Mailer 提供支援。Laravel 允許您註冊自定義回撥,這些回撥將在傳送訊息之前與 Symfony Message 例項一起呼叫。這使您有機會在訊息傳送之前對其進行深入自定義。為此,請在 Envelope 定義中定義一個 using 引數
1use Illuminate\Mail\Mailables\Envelope; 2use Symfony\Component\Mime\Email; 3 4/** 5 * Get the message envelope. 6 */ 7public function envelope(): Envelope 8{ 9 return new Envelope(10 subject: 'Order Shipped',11 using: [12 function (Email $message) {13 // ...14 },15 ]16 );17}
Markdown 郵件類
Markdown 郵件訊息允許您在郵件類中利用 郵件通知 的預構建模板和元件。由於訊息是用 Markdown 編寫的,Laravel 能夠為這些訊息呈現美觀、響應式的 HTML 模板,同時自動生成純文字對應內容。
生成 Markdown 郵件類
要生成帶有相應 Markdown 模板的郵件類,可以使用 make:mail Artisan 命令的 --markdown 選項
1php artisan make:mail OrderShipped --markdown=mail.orders.shipped
然後,在 content 方法中配置郵件類的 Content 定義時,使用 markdown 引數代替 view 引數
1use Illuminate\Mail\Mailables\Content; 2 3/** 4 * Get the message content definition. 5 */ 6public function content(): Content 7{ 8 return new Content( 9 markdown: 'mail.orders.shipped',10 with: [11 'url' => $this->orderUrl,12 ],13 );14}
編寫 Markdown 訊息
Markdown 郵件類結合了 Blade 元件和 Markdown 語法,使您可以輕鬆構建電子郵件訊息,同時利用 Laravel 的預構建電子郵件 UI 元件
1<x-mail::message> 2# Order Shipped 3 4Your order has been shipped! 5 6<x-mail::button :url="$url"> 7View Order 8</x-mail::button> 9 10Thanks,<br>11{{ config('app.name') }}12</x-mail::message>
編寫 Markdown 電子郵件時不要使用過多的縮排。根據 Markdown 標準,Markdown 解析器會將縮排的內容呈現為程式碼塊。
按鈕元件
按鈕元件呈現一個居中的按鈕連結。該元件接受兩個引數,一個 url 和一個可選的 color。支援的顏色有 primary、success 和 error。您可以在訊息中新增任意數量的按鈕元件
1<x-mail::button :url="$url" color="success">2View Order3</x-mail::button>
面板元件
面板元件在面板中呈現給定的文字塊,其背景顏色與郵件的其他部分略有不同。這允許您引起對特定文字塊的注意
1<x-mail::panel>2This is the panel content.3</x-mail::panel>
表格元件
表格元件允許您將 Markdown 表格轉換為 HTML 表格。該元件接受 Markdown 表格作為其內容。表格列對齊方式支援使用標準的 Markdown 表格對齊語法
1<x-mail::table>2| Laravel | Table | Example |3| ------------- | :-----------: | ------------: |4| Col 2 is | Centered | $10 |5| Col 3 is | Right-Aligned | $20 |6</x-mail::table>
自定義元件
您可以將所有的 Markdown 郵件元件匯出到您自己的應用程式中進行自定義。要匯出元件,請使用 vendor:publish Artisan 命令釋出 laravel-mail 資產標籤
1php 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 表示形式中的內聯 CSS 樣式。
如果您想為 Laravel 的 Markdown 元件構建一個全新的主題,可以將 CSS 檔案放置在 html/themes 目錄中。命名並儲存 CSS 檔案後,更新應用程式 config/mail.php 配置檔案中的 theme 選項以匹配新主題的名稱。
要為單個郵件類自定義主題,您可以將郵件類的 $theme 屬性設定為傳送該郵件時應使用的主題名稱。
傳送郵件
要傳送訊息,請使用 Mail 外觀 (facade) 上的 to 方法。to 方法接受電子郵件地址、使用者例項或使用者集合。如果您傳遞物件或物件集合,郵件程式將自動使用其 email 和 name 屬性來確定電子郵件的收件人,因此請確保這些屬性在您的物件上可用。指定收件人後,您可以將郵件類的例項傳遞給 send 方法
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Mail\OrderShipped; 6use App\Models\Order; 7use Illuminate\Http\RedirectResponse; 8use Illuminate\Http\Request; 9use Illuminate\Support\Facades\Mail;10 11class OrderShipmentController extends Controller12{13 /**14 * Ship the given order.15 */16 public function store(Request $request): RedirectResponse17 {18 $order = Order::findOrFail($request->order_id);19 20 // Ship the order...21 22 Mail::to($request->user())->send(new OrderShipped($order));23 24 return redirect('/orders');25 }26}
傳送訊息時,您不限於僅指定“to”收件人。您可以透過鏈式呼叫各自的方法來自由設定“to”、“cc”和“bcc”收件人
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->send(new OrderShipped($order));
迴圈處理收件人
有時,您可能需要透過遍歷收件人/電子郵件地址陣列來向收件人列表傳送郵件。但是,由於 to 方法會將電子郵件地址追加到郵件的收件人列表中,迴圈的每次迭代都會向之前的每個收件人傳送另一封電子郵件。因此,您應該始終為每個收件人重新建立郵件例項
2 Mail::to($recipient)->send(new OrderShipped($order));3}
透過特定郵件程式傳送郵件
預設情況下,Laravel 將使用在應用程式 mail 配置檔案中配置為 default 郵件程式的郵件程式傳送電子郵件。但是,您可以使用 mailer 方法使用特定的郵件程式配置來發送訊息
1Mail::mailer('postmark')2 ->to($request->user())3 ->send(new OrderShipped($order));
郵件佇列
將郵件訊息加入佇列
由於傳送電子郵件訊息可能會對應用程式的響應時間產生負面影響,因此許多開發人員選擇將電子郵件訊息加入佇列以進行後臺傳送。Laravel 使用其內建的 統一佇列 API 使此操作變得簡單。要將郵件訊息加入佇列,請在指定訊息的收件人後,使用 Mail 外觀上的 queue 方法
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->queue(new OrderShipped($order));
此方法將自動負責將作業推送到佇列,以便在後臺傳送訊息。在使用此功能之前,您需要 配置您的佇列。
延遲訊息佇列
如果您希望延遲排隊電子郵件訊息的傳送,可以使用 later 方法。later 方法的第一個引數接受一個 DateTime 例項,指示訊息應在何時傳送
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->later(now()->plus(minutes: 10), new OrderShipped($order));
推送到特定佇列
由於使用 make:mail 命令生成的所有郵件類都使用了 Illuminate\Bus\Queueable 特徵 (trait),因此您可以在任何郵件類例項上呼叫 onQueue 和 onConnection 方法,從而指定訊息的連線和佇列名稱
1$message = (new OrderShipped($order))2 ->onConnection('sqs')3 ->onQueue('emails');4 5Mail::to($request->user())6 ->cc($moreUsers)7 ->bcc($evenMoreUsers)8 ->queue($message);
預設加入佇列
如果您有希望始終加入佇列的郵件類,可以實現該類上的 ShouldQueue 合約。現在,即使您在傳送郵件時呼叫了 send 方法,由於它實現了該合約,郵件仍會被加入佇列
1use Illuminate\Contracts\Queue\ShouldQueue;2 3class OrderShipped extends Mailable implements ShouldQueue4{5 // ...6}
排隊的郵件類與資料庫事務
當排隊的郵件在資料庫事務中被分發時,它們可能會在資料庫事務提交之前被佇列處理。發生這種情況時,您在資料庫事務期間對模型或資料庫記錄所做的任何更新可能尚未在資料庫中反映出來。此外,在事務中建立的任何模型或資料庫記錄可能在資料庫中尚不存在。如果您的郵件類依賴於這些模型,則在處理傳送排隊郵件的作業時可能會發生意外錯誤。
如果您的佇列連線的 after_commit 配置選項設定為 false,您仍然可以透過在傳送郵件訊息時呼叫 afterCommit 方法,來指示特定的排隊郵件應在所有開啟的資料庫事務提交後才進行分發
1Mail::to($request->user())->send(2 (new OrderShipped($order))->afterCommit()3);
或者,您可以從郵件類的建構函式中呼叫 afterCommit 方法
1<?php 2 3namespace App\Mail; 4 5use Illuminate\Bus\Queueable; 6use Illuminate\Contracts\Queue\ShouldQueue; 7use Illuminate\Mail\Mailable; 8use Illuminate\Queue\SerializesModels; 9 10class OrderShipped extends Mailable implements ShouldQueue11{12 use Queueable, SerializesModels;13 14 /**15 * Create a new message instance.16 */17 public function __construct()18 {19 $this->afterCommit();20 }21}
要了解更多關於解決這些問題的資訊,請檢視關於排隊作業與資料庫事務的文件。
排隊郵件失敗
當排隊的電子郵件失敗時,如果已定義,則將呼叫排隊郵件類上的 failed 方法。導致排隊電子郵件失敗的 Throwable 例項將被傳遞給 failed 方法
1<?php 2 3namespace App\Mail; 4 5use Illuminate\Contracts\Queue\ShouldQueue; 6use Illuminate\Mail\Mailable; 7use Illuminate\Queue\SerializesModels; 8use Throwable; 9 10class OrderDelayed extends Mailable implements ShouldQueue11{12 use SerializesModels;13 14 /**15 * Handle a queued email's failure.16 */17 public function failed(Throwable $exception): void18 {19 // ...20 }21}
渲染郵件類
有時您可能希望在不傳送的情況下捕獲郵件的 HTML 內容。為此,您可以呼叫郵件類的 render 方法。此方法將以字串形式返回郵件的已求值 HTML 內容
1use App\Mail\InvoicePaid;2use App\Models\Invoice;3 4$invoice = Invoice::find(1);5 6return (new InvoicePaid($invoice))->render();
在瀏覽器中預覽郵件類
在設計郵件模板時,像典型的 Blade 模板一樣在瀏覽器中快速預覽渲染後的郵件非常方便。因此,Laravel 允許您直接從路由閉包或控制器返回任何郵件。返回郵件後,它將被渲染並顯示在瀏覽器中,從而允許您快速預覽其設計,而無需將其傳送到實際的電子郵件地址
1Route::get('/mailable', function () {2 $invoice = App\Models\Invoice::find(1);3 4 return new App\Mail\InvoicePaid($invoice);5});
郵件本地化
Laravel 允許您以非請求當前區域設定的其他區域設定傳送郵件,即使郵件被排隊,它也會記住此區域設定。
為此,Mail 外觀提供了一個 locale 方法來設定所需的語言。當評估郵件的模板時,應用程式將切換到此區域設定,並在評估完成後恢復到之前的區域設定
1Mail::to($request->user())->locale('es')->send(2 new OrderShipped($order)3);
使用者偏好區域設定
有時,應用程式會儲存每個使用者的首選區域設定。透過在一個或多個模型上實現 HasLocalePreference 合約,您可以指示 Laravel 在傳送郵件時使用此儲存的區域設定
1use Illuminate\Contracts\Translation\HasLocalePreference; 2 3class User extends Model implements HasLocalePreference 4{ 5 /** 6 * Get the user's preferred locale. 7 */ 8 public function preferredLocale(): string 9 {10 return $this->locale;11 }12}
實現介面後,Laravel 在向模型傳送郵件和通知時會自動使用首選區域設定。因此,使用此介面時無需呼叫 locale 方法
1Mail::to($request->user())->send(new OrderShipped($order));
測試
測試郵件內容
Laravel 提供了多種方法來檢查郵件的結構。此外,Laravel 還提供了幾種方便的方法來測試您的郵件是否包含您預期的內容
1use App\Mail\InvoicePaid; 2use App\Models\User; 3 4test('mailable content', function () { 5 $user = User::factory()->create(); 6 7 $mailable = new InvoicePaid($user); 8 14 $mailable->assertHasSubject('Invoice Paid');15 $mailable->assertHasTag('example-tag');16 $mailable->assertHasMetadata('key', 'value');17 18 $mailable->assertSeeInHtml($user->email);19 $mailable->assertDontSeeInHtml('Invoice Not Paid');20 $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);21 22 $mailable->assertSeeInText($user->email);23 $mailable->assertDontSeeInText('Invoice Not Paid');24 $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);25 26 $mailable->assertHasAttachment('/path/to/file');27 $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));28 $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);29 $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);30 $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);31});
1use App\Mail\InvoicePaid; 2use App\Models\User; 3 4public function test_mailable_content(): void 5{ 6 $user = User::factory()->create(); 7 8 $mailable = new InvoicePaid($user); 9 15 $mailable->assertHasSubject('Invoice Paid');16 $mailable->assertHasTag('example-tag');17 $mailable->assertHasMetadata('key', 'value');18 19 $mailable->assertSeeInHtml($user->email);20 $mailable->assertDontSeeInHtml('Invoice Not Paid');21 $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);22 23 $mailable->assertSeeInText($user->email);24 $mailable->assertDontSeeInText('Invoice Not Paid');25 $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);26 27 $mailable->assertHasAttachment('/path/to/file');28 $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));29 $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);30 $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);31 $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);32}
正如您所料,“HTML”斷言斷言郵件的 HTML 版本包含給定字串,而“text”斷言斷言郵件的純文字版本包含給定字串。
測試郵件傳送
我們建議將郵件內容的測試與斷言特定郵件已“傳送”給特定使用者的測試分開。通常,郵件內容與您正在測試的程式碼無關,只需斷言 Laravel 已被指示傳送給定的郵件就足夠了。
您可以使用 Mail 外觀的 fake 方法來阻止傳送郵件。呼叫 Mail 外觀的 fake 方法後,您可以斷言郵件已被指示傳送給使用者,甚至可以檢查郵件收到的資料
1<?php 2 3use App\Mail\OrderShipped; 4use Illuminate\Support\Facades\Mail; 5 6test('orders can be shipped', function () { 7 Mail::fake(); 8 9 // Perform order shipping...10 11 // Assert that no mailables were sent...12 Mail::assertNothingSent();13 14 // Assert that a mailable was sent...15 Mail::assertSent(OrderShipped::class);16 17 // Assert a mailable was sent twice...18 Mail::assertSent(OrderShipped::class, 2);19 20 // Assert a mailable was sent to an email address...22 23 // Assert a mailable was sent to multiple email addresses...25 26 // Assert a mailable was not sent...27 Mail::assertNotSent(AnotherMailable::class);28 29 // Assert a mailable was sent twice...30 Mail::assertSentTimes(OrderShipped::class, 2);31 32 // Assert 3 total mailables were sent...33 Mail::assertSentCount(3);34});
1<?php 2 3namespace Tests\Feature; 4 5use App\Mail\OrderShipped; 6use Illuminate\Support\Facades\Mail; 7use Tests\TestCase; 8 9class ExampleTest extends TestCase10{11 public function test_orders_can_be_shipped(): void12 {13 Mail::fake();14 15 // Perform order shipping...16 17 // Assert that no mailables were sent...18 Mail::assertNothingSent();19 20 // Assert that a mailable was sent...21 Mail::assertSent(OrderShipped::class);22 23 // Assert a mailable was sent twice...24 Mail::assertSent(OrderShipped::class, 2);25 26 // Assert a mailable was sent to an email address...28 29 // Assert a mailable was sent to multiple email addresses...31 32 // Assert a mailable was not sent...33 Mail::assertNotSent(AnotherMailable::class);34 35 // Assert a mailable was sent twice...36 Mail::assertSentTimes(OrderShipped::class, 2);37 38 // Assert 3 total mailables were sent...39 Mail::assertSentCount(3);40 }41}
如果您正在將郵件排隊以在後臺傳送,則應使用 assertQueued 方法而不是 assertSent
1Mail::assertQueued(OrderShipped::class);2Mail::assertNotQueued(OrderShipped::class);3Mail::assertNothingQueued();4Mail::assertQueuedCount(3);
您還可以使用 assertOutgoingCount 方法斷言已傳送或排隊的郵件總數
1Mail::assertOutgoingCount(3);
您可以向 assertSent、assertNotSent、assertQueued 或 assertNotQueued 方法傳遞一個閉包,以斷言傳送的郵件通過了給定的“真值測試”。如果至少有一封傳送的郵件通過了給定的真值測試,則斷言將成功
1Mail::assertSent(function (OrderShipped $mail) use ($order) {2 return $mail->order->id === $order->id;3});
呼叫 Mail 外觀的斷言方法時,所提供閉包接收到的郵件例項公開了有助於檢查郵件的有用方法
1Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) { 2 return $mail->hasTo($user->email) && 3 $mail->hasCc('...') && 4 $mail->hasBcc('...') && 5 $mail->hasReplyTo('...') && 6 $mail->hasFrom('...') && 7 $mail->hasSubject('...') && 8 $mail->hasMetadata('order_id', $mail->order->id); 9 $mail->usesMailer('ses');10});
郵件例項還包括幾個有助於檢查郵件附件的有用方法
1use Illuminate\Mail\Mailables\Attachment; 2 3Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { 4 return $mail->hasAttachment( 5 Attachment::fromPath('/path/to/file') 6 ->as('name.pdf') 7 ->withMime('application/pdf') 8 ); 9});10 11Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {12 return $mail->hasAttachment(13 Attachment::fromStorageDisk('s3', '/path/to/file')14 );15});16 17Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) {18 return $mail->hasAttachment(19 Attachment::fromData(fn () => $pdfData, 'name.pdf')20 );21});
您可能已經注意到有兩個用於斷言郵件未傳送的方法:assertNotSent 和 assertNotQueued。有時您可能希望斷言沒有郵件被髮送 或 排隊。為此,您可以使用 assertNothingOutgoing 和 assertNotOutgoing 方法
1Mail::assertNothingOutgoing();2 3Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) {4 return $mail->order->id === $order->id;5});
郵件與本地開發
在開發傳送電子郵件的應用程式時,您可能不想真正向真實的電子郵件地址傳送郵件。Laravel 提供了幾種在本地開發過程中“停用”實際傳送電子郵件的方法。
日誌驅動程式
log 郵件驅動程式不會發送您的電子郵件,而是將所有電子郵件訊息寫入日誌檔案以供檢查。通常,此驅動程式僅在本地開發中使用。有關針對不同環境配置應用程式的更多資訊,請檢視 配置文件。
HELO / Mailtrap / Mailpit
或者,您可以使用 HELO 或 Mailtrap 等服務以及 smtp 驅動程式將您的電子郵件訊息傳送到“虛擬”郵箱,在那裡您可以在真正的電子郵件客戶端中檢視它們。這種方法的好處是允許您在 Mailtrap 的訊息檢視器中實際檢查最終的電子郵件。
如果您使用的是 Laravel Sail,則可以使用 Mailpit 預覽您的訊息。當 Sail 執行時,您可以在以下地址訪問 Mailpit 介面:https://:8025。
使用全域性 to 地址
最後,您可以透過呼叫 Mail 外觀提供的 alwaysTo 方法來指定全域性“to”地址。通常,此方法應從應用程式服務提供商之一的 boot 方法中呼叫
1use Illuminate\Support\Facades\Mail; 2 3/** 4 * Bootstrap any application services. 5 */ 6public function boot(): void 7{ 8 if ($this->app->environment('local')) {10 }11}
使用 alwaysTo 方法時,郵件訊息上的任何額外“cc”或“bcc”地址都將被刪除。
活動
Laravel 在傳送郵件訊息時會分發兩個事件。MessageSending 事件在訊息傳送之前分發,而 MessageSent 事件在訊息傳送之後分發。請記住,這些事件是在郵件 正在傳送 時分發的,而不是在郵件被排隊時分發的。您可以在應用程式中為這些事件建立 事件監聽器
1use Illuminate\Mail\Events\MessageSending; 2// use Illuminate\Mail\Events\MessageSent; 3 4class LogMessage 5{ 6 /** 7 * Handle the event. 8 */ 9 public function handle(MessageSending $event): void10 {11 // ...12 }13}
自定義傳輸方式
Laravel 包含了多種郵件傳輸器;但是,您可能希望編寫自己的傳輸器,透過 Laravel 開箱即不支援的其他服務傳送電子郵件。首先,定義一個繼承 Symfony\Component\Mailer\Transport\AbstractTransport 類的類。然後,在您的傳輸器上實現 doSend 和 __toString 方法
1<?php 2 3namespace App\Mail; 4 5use MailchimpTransactional\ApiClient; 6use Symfony\Component\Mailer\SentMessage; 7use Symfony\Component\Mailer\Transport\AbstractTransport; 8use Symfony\Component\Mime\Address; 9use Symfony\Component\Mime\MessageConverter;10 11class MailchimpTransport extends AbstractTransport12{13 /**14 * Create a new Mailchimp transport instance.15 */16 public function __construct(17 protected ApiClient $client,18 ) {19 parent::__construct();20 }21 22 /**23 * {@inheritDoc}24 */25 protected function doSend(SentMessage $message): void26 {27 $email = MessageConverter::toEmail($message->getOriginalMessage());28 29 $this->client->messages->send(['message' => [30 'from_email' => $email->getFrom(),31 'to' => collect($email->getTo())->map(function (Address $email) {32 return ['email' => $email->getAddress(), 'type' => 'to'];33 })->all(),34 'subject' => $email->getSubject(),35 'text' => $email->getTextBody(),36 ]]);37 }38 39 /**40 * Get the string representation of the transport.41 */42 public function __toString(): string43 {44 return 'mailchimp';45 }46}
定義好自定義傳輸器後,可以透過 Mail 外觀提供的 extend 方法註冊它。通常,這應該在應用程式的 AppServiceProvider 的 boot 方法中完成。一個 $config 引數將被傳遞給提供給 extend 方法的閉包。此引數將包含為應用程式 config/mail.php 配置檔案中的郵件程式定義的配置陣列
1use App\Mail\MailchimpTransport; 2use Illuminate\Support\Facades\Mail; 3use MailchimpTransactional\ApiClient; 4 5/** 6 * Bootstrap any application services. 7 */ 8public function boot(): void 9{10 Mail::extend('mailchimp', function (array $config = []) {11 $client = new ApiClient;12 13 $client->setApiKey($config['key']);14 15 return new MailchimpTransport($client);16 });17}
定義並註冊好自定義傳輸器後,您可以在應用程式的 config/mail.php 配置檔案中建立一個使用新傳輸器的郵件程式定義
1'mailchimp' => [2 'transport' => 'mailchimp',3 'key' => env('MAILCHIMP_API_KEY'),4 // ...5],
其他 Symfony 傳輸方式
Laravel 包含對一些現有的由 Symfony 維護的郵件傳輸器(如 Mailgun 和 Postmark)的支援。但是,您可能希望擴充套件 Laravel 以支援其他由 Symfony 維護的傳輸器。您可以透過 Composer 要求必要的 Symfony 郵件程式並向 Laravel 註冊該傳輸器來做到這一點。例如,您可以安裝並註冊“Brevo”(前身為“Sendinblue”)Symfony 郵件程式
1composer require symfony/brevo-mailer symfony/http-client
安裝 Brevo 郵件程式包後,您可以將 Brevo API 憑證的條目新增到應用程式的 services 配置檔案中
1'brevo' => [2 'key' => env('BREVO_API_KEY'),3],
接下來,您可以使用 Mail 外觀的 extend 方法向 Laravel 註冊該傳輸器。通常,這應該在服務提供商的 boot 方法中完成
1use Illuminate\Support\Facades\Mail; 2use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; 3use Symfony\Component\Mailer\Transport\Dsn; 4 5/** 6 * Bootstrap any application services. 7 */ 8public function boot(): void 9{10 Mail::extend('brevo', function () {11 return (new BrevoTransportFactory)->create(12 new Dsn(13 'brevo+api',14 'default',15 config('services.brevo.key')16 )17 );18 });19}
註冊好傳輸器後,您可以在應用程式的 config/mail.php 配置檔案中建立一個使用新傳輸器的郵件程式定義
1'brevo' => [2 'transport' => 'brevo',3 // ...4],