郵件
簡介
傳送郵件不會很複雜。Laravel 提供簡潔的 API,並由熱門的 SwiftMailer 函式庫驅動。Laravel 與 SwiftMailer 提供使用 SMTP、Mailgun、Postmark、Amazon SES、sendmail
等方式寄信的 Driver,可讓我們使用偏好的本機或雲端服務來快速開始傳送郵件。
設定
可以使用專案的 config/mail.php
設定檔來設定 Laravel 的郵件服務。在這個檔案中,每個 Mailer 都可以有不同的設定,甚至還可以設定不同的「Transport」設定,這樣我們就可以在程式中使用不同的電子郵件服務來寄送不同的訊息。舉例來說,我們可以使用 Postmark 來寄送交易電子郵件,並使用 Amazon SES 來傳送大量寄送的電子郵件。
在 mail
設定檔中,可以看到一個 mailers
設定陣列。這個陣列中包含了 Laravel 支援的各個主要郵件 Driver / Transport 範例設定,而其中 default
設定值用來判斷專案預設要使用哪個 Mailer 來傳送電子郵件訊息。
Driver / Transport 的前置要求
如 Mailgun 與 Postmark 等基於 API 的 Driver 在寄送郵件時通常會比 SMTP 伺服器來得簡單快速。若可能的話,我們建議你從這幾個 Driver 中選一個使用。這些基於 API 的 Driver 都要求要有 Guzzle HTTP 函式庫,可以通過 Composer 套件管理員來安裝 Guzzle HTTP 函式庫:
1composer require guzzlehttp/guzzle
1composer require guzzlehttp/guzzle
Mailgun Driver
若要使用 Mailgun Driver,請先安裝 Guzzle HTTP 函式庫。接著,在 config/mail.php
設定檔中將 default
選項設為 mailgun
。接著,請確認一下 config/services.php
設定檔中是否包含下列選項:
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4],
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4],
若你使用的 Mailgun 地區不是美國的話,請在 services
設定檔中定義該地區的 Endpoint:
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),5],
1'mailgun' => [2 'domain' => env('MAILGUN_DOMAIN'),3 'secret' => env('MAILGUN_SECRET'),4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),5],
Postmark Driver
若要使用 Postmark Driver,請使用 Composer 安裝 Postmark 的 SwiftMailer Transport:
1composer require wildbit/swiftmailer-postmark
1composer require wildbit/swiftmailer-postmark
接著,請安裝 Guzzle HTTP 函式庫。然後,在 config/mail.php
設定檔中將 default
選項設為 postmark
。最後,請確認一下 config/services.php
設定檔中是否包含下列選項:
1'postmark' => [2 'token' => env('POSTMARK_TOKEN'),3],
1'postmark' => [2 'token' => env('POSTMARK_TOKEN'),3],
若想為給定 Mailer 指定 Postmark 訊息串流,請在該 Mailer 的設定陣列中加上 message_stream_id
設定選項。該設定陣列可在 config/mail.php
設定檔中找到:
1'postmark' => [2 'transport' => 'postmark',3 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),4],
1'postmark' => [2 'transport' => 'postmark',3 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),4],
這樣一來,我們就能設定多個 Postmark Mailer,並給不同 Mailer 設定不同的訊息串流。
SES Driver
若要使用 Amazon SES Driver,必須先安裝 PHP 版的 Amazon SDK。可使用 Composer 套件管理員來安裝這個函式庫:
1composer require aws/aws-sdk-php
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],
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],
若要通過 Session Token 使用 AWS 的 Temporary Credential,請在專案的 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],
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],
若想定義要讓 Laravel 在寄送郵件時要傳給 AWS SDK 之 SendRawEmail
方法的額外的選項,可在 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 'Tags' => [8 ['Name' => 'foo', 'Value' => 'bar'],9 ],10 ],11],
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 'Tags' => [8 ['Name' => 'foo', 'Value' => 'bar'],9 ],10 ],11],
Failover 設定
有時候,我們設定要用來寄送郵件的外部服務可能沒辦法用。因為這種情況,所以最好定義一個或多個備用的郵件寄送設定,以免主要寄送 Driver 無法使用。
若要定義備用 Mailer,請在 mail
設定檔中定義一個使用 failover
Transport的 Mailer。failover
Mailer的設定值呢列應包含一個 mailers
的陣列,並在其中參照用來寄送郵件之各個 Driver 的順序:
1'mailers' => [2 'failover' => [3 'transport' => 'failover',4 'mailers' => [5 'postmark',6 'mailgun',7 'sendmail',8 ],9 ],1011 // ...12],
1'mailers' => [2 'failover' => [3 'transport' => 'failover',4 'mailers' => [5 'postmark',6 'mailgun',7 'sendmail',8 ],9 ],1011 // ...12],
定義好 Failover Mailer 後,請將 mail
設定檔中的 default
設定索引鍵設為該 Failover Mailer 的名稱,以將其設為預設 Mailer。
1'default' => env('MAIL_MAILER', 'failover'),
1'default' => env('MAIL_MAILER', 'failover'),
產生 Mailable
在撰寫 Laravel 專案時,程式所寄出的所有郵件都以「Mailable」類別的形式呈現。這些類別保存在 app/Mail
目錄中。若沒看到這個目錄,請別擔心。使用 make:mail
Artisan 指令初次建立 Mailable 類別時會自動產生該目錄:
1php artisan make:mail OrderShipped
1php artisan make:mail OrderShipped
撰寫 Mailable
產生好 Mailable 類別後,請打開該類別,我們來看看裡面的內容。首先,可以注意到所有的 Mailable 類別都在 build
方法內進行設定。在該方法中,可呼叫如 form
、view
、attach
等方法來設定 E-Mail 的顯示方式與寄送設定。
也可以在 Mailable 的 build
方法上對相依性項目進行型別提示。Laravel 的 Service Container 會自動插入這些相依性項目。
設定寄件人
使用 from
方法
首先,我們先來看看如何設定寄件人。或者,換句話說,也就是郵件要「從」誰那裡寄出。要設定寄件人,有兩種方法。第一種方法,我們可以在 Mailable 類別的 build
方法內使用 from
方法來設定:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{9 ->view('emails.orders.shipped');10}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{9 ->view('emails.orders.shipped');10}
使用全域的 from
位址
不過,若你的專案中所有的郵件都使用相同的寄件人位址,在每個產生的 Mailable 類別內都呼叫 from
方法會很麻煩。比起在每個 Mailable 內呼叫 from
方法,我們可以在 config/mail.php
設定檔中指定一個全域的「from」位址。若 Mailable 類別內沒有指定「from」位址,就會使用這個全域的位址:
此外,也可以在 config/mail.php
設定檔中定義一個全域的「reply_to」位址:
設定 View
在 Mailable 類別的 build
方法中,可以使用 view
方法來指定在轉譯郵件內容時欲使用哪個樣板。由於一般來說大部分郵件都是使用 [Blade 樣板]來轉譯內容的,因此在建立郵件內容時,我們就可以使用 Blade 樣板引擎的完整功能與便利:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped');9}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped');9}
可以建立一個 resources/views/emails
目錄來放置所有的郵件樣板。不過,不一定要放在這個目錄,可以隨意放在 resources/views
目錄下。
純文字郵件
若想為郵件定義純文字版本,可使用 text
方法。與 view
方法一樣,text
方法接受一個用來轉譯郵件內容的樣板名稱。可以同時為郵件定義 HTML 與純文字版本:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->text('emails.orders.shipped_plain');10}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->text('emails.orders.shipped_plain');10}
View 資料
使用公開屬性
一般來說,在轉譯 HTML 版本的郵件時,我們會需要將資料傳入 View 來在其中使用。要將資料傳入 View 有兩種方法。第一種方法,即是在 Mailable 類別裡的公用變數,在 View 裡面可以直接使用。因此,舉例來說,我們可以將資料傳入 Mailable 類別的 Constructor 內,然後將資料設為該類別中定義的公用變數:
1<?php23namespace App\Mail;45use App\Models\Order;6use Illuminate\Bus\Queueable;7use Illuminate\Mail\Mailable;8use Illuminate\Queue\SerializesModels;910class OrderShipped extends Mailable11{12 use Queueable, SerializesModels;1314 /**15 * The order instance.16 *17 * @var \App\Models\Order18 */19 public $order;2021 /**22 * Create a new message instance.23 *24 * @param \App\Models\Order $order25 * @return void26 */27 public function __construct(Order $order)28 {29 $this->order = $order;30 }3132 /**33 * Build the message.34 *35 * @return $this36 */37 public function build()38 {39 return $this->view('emails.orders.shipped');40 }41}
1<?php23namespace App\Mail;45use App\Models\Order;6use Illuminate\Bus\Queueable;7use Illuminate\Mail\Mailable;8use Illuminate\Queue\SerializesModels;910class OrderShipped extends Mailable11{12 use Queueable, SerializesModels;1314 /**15 * The order instance.16 *17 * @var \App\Models\Order18 */19 public $order;2021 /**22 * Create a new message instance.23 *24 * @param \App\Models\Order $order25 * @return void26 */27 public function __construct(Order $order)28 {29 $this->order = $order;30 }3132 /**33 * Build the message.34 *35 * @return $this36 */37 public function build()38 {39 return $this->view('emails.orders.shipped');40 }41}
將資料設為公用變數後,在 View 中就自動可以使用該資料。因此在 Blade 樣板中,我們可以像存取其他資料一樣存取這些資料:
1<div>2 Price: {{ $order->price }}3</div>
1<div>2 Price: {{ $order->price }}3</div>
通過 with
方法:
若想在資料被傳給樣板前自訂其格式,可使用 with
方法來手動傳入資料。一般來說,我們還是會使用 Mailable 類別的 Constroctor 來傳入資料。不過,我們可以將該資料設為 protected
或 private
屬性,這樣樣板中才不會有這些資料。接著,呼叫 with
方法,傳入欲在樣板中使用的資料:
1<?php23namespace App\Mail;45use App\Models\Order;6use Illuminate\Bus\Queueable;7use Illuminate\Mail\Mailable;8use Illuminate\Queue\SerializesModels;910class OrderShipped extends Mailable11{12 use Queueable, SerializesModels;1314 /**15 * The order instance.16 *17 * @var \App\Models\Order18 */19 protected $order;2021 /**22 * Create a new message instance.23 *24 * @param \App\Models\Order $order25 * @return void26 */27 public function __construct(Order $order)28 {29 $this->order = $order;30 }3132 /**33 * Build the message.34 *35 * @return $this36 */37 public function build()38 {39 return $this->view('emails.orders.shipped')40 ->with([41 'orderName' => $this->order->name,42 'orderPrice' => $this->order->price,43 ]);44 }45}
1<?php23namespace App\Mail;45use App\Models\Order;6use Illuminate\Bus\Queueable;7use Illuminate\Mail\Mailable;8use Illuminate\Queue\SerializesModels;910class OrderShipped extends Mailable11{12 use Queueable, SerializesModels;1314 /**15 * The order instance.16 *17 * @var \App\Models\Order18 */19 protected $order;2021 /**22 * Create a new message instance.23 *24 * @param \App\Models\Order $order25 * @return void26 */27 public function __construct(Order $order)28 {29 $this->order = $order;30 }3132 /**33 * Build the message.34 *35 * @return $this36 */37 public function build()38 {39 return $this->view('emails.orders.shipped')40 ->with([41 'orderName' => $this->order->name,42 'orderPrice' => $this->order->price,43 ]);44 }45}
使用 with
方法傳入資料後,在 View 中就自動可以使用該資料。因此在 Blade 樣板中,我們可以像存取其他資料一樣存取這些資料:
1<div>2 Price: {{ $orderPrice }}3</div>
1<div>2 Price: {{ $orderPrice }}3</div>
附加檔案
若要將檔案附加至 E-Mail,請使用 Mailable 類別 build
方法中的 attach
方法。attach
方法接受檔案的完整路徑作為其第一個引數:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attach('/path/to/file');10}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attach('/path/to/file');10}
將檔案附加至訊息時,也可傳入一個陣列給 attach
方法來指定要顯示的檔案名稱與 / 或 MIME 類型:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attach('/path/to/file', [10 'as' => 'name.pdf',11 'mime' => 'application/pdf',12 ]);13}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attach('/path/to/file', [10 'as' => 'name.pdf',11 'mime' => 'application/pdf',12 ]);13}
從 Disk 中附加檔案
若有儲存在檔案系統 Disk中的檔案,可使用 attachFromStorage
方法來將其附加至郵件中:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachFromStorage('/path/to/file');10}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachFromStorage('/path/to/file');10}
若有需要,可使用 attachFromStorage
方法的第三與第四個引數來指定檔案名稱與其他額外的選項:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachFromStorage('/path/to/file', 'name.pdf', [10 'mime' => 'application/pdf'11 ]);12}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachFromStorage('/path/to/file', 'name.pdf', [10 'mime' => 'application/pdf'11 ]);12}
若想指定預設以外的 Disk,可使用 attachFromStorageDisk
方法:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachFromStorageDisk('s3', '/path/to/file');10}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachFromStorageDisk('s3', '/path/to/file');10}
原始資料附加檔案
可使用 attachData
方法來以位元組原始字串的形式作為附件附加。舉例來說,我們可能會在記憶體內產生 PDF,然後想在不寫入 Disk 的情況下將其附加到郵件上。attachData
方法接受原始資料位元組作為其第一個引數,檔案名稱為其第二個引數,然後是一組選項陣列作為其第三個引數:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachData($this->pdf, 'name.pdf', [10 'mime' => 'application/pdf',11 ]);12}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 return $this->view('emails.orders.shipped')9 ->attachData($this->pdf, 'name.pdf', [10 'mime' => 'application/pdf',11 ]);12}
內嵌的附加檔案
一般來說,要把圖片內嵌到郵件裡面是很麻煩的。不過,Laravel 提供了一個方便的方法可以將圖片附加到郵件裡。若要內嵌圖片,請使用郵件樣板內 $message
變數中的 embed
方法。Laravel 會自動為所有的郵件樣板提供這個 $message
變數,所以我們不需要手動傳入:
1<body>2 Here is an image:34 <img src="{{ $message->embed($pathToImage) }}">5</body>
1<body>2 Here is an image:34 <img src="{{ $message->embed($pathToImage) }}">5</body>
$message
變數無法在純文字訊息樣板中使用,因為純文字樣板無法使用內嵌的附加檔案。
內嵌原始資料附件
若有欲嵌入到郵件樣板中的原始圖片字串,可呼叫 $message
變數上的 embedData
方法。呼叫 embedData
方法時,請提供一個欲設定給嵌入圖片的檔案名稱:
1<body>2 Here is an image from raw data:34 <img src="{{ $message->embedData($data, 'example-image.jpg') }}">5</body>
1<body>2 Here is an image from raw data:34 <img src="{{ $message->embedData($data, 'example-image.jpg') }}">5</body>
自訂 SwiftMailer 訊息
Mailable
基礎類別的 withSwiftMessage
方法可讓我們註冊一個閉包,在傳送訊息前會以 SwiftMailer 實體叫用該閉包。這樣我們就有機會在郵件被送出前深度自訂該訊息:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 $this->view('emails.orders.shipped');910 $this->withSwiftMessage(function ($message) {11 $message->getHeaders()->addTextHeader(12 'Custom-Header', 'Header Value'13 );14 });1516 return $this;17}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{8 $this->view('emails.orders.shipped');910 $this->withSwiftMessage(function ($message) {11 $message->getHeaders()->addTextHeader(12 'Custom-Header', 'Header Value'13 );14 });1516 return $this;17}
Markdown 的 Mailer
Markdown Mailer 訊息可讓我們在 Mailable 內使用內建樣板與 Mail Notification 的元件。由於使用 Markdown 來撰寫訊息,因此 Laravel 就可為這些郵件轉譯出漂亮的回應式 HTML 樣板,並自動轉譯出純文字版本的郵件。
產生 Markdown 的 Malable
若要產生有對應 Markdown 樣板的 Mailable,請使用 make:mail
Artisan 指令的 --markdown
選項:
1php artisan make:mail OrderShipped --markdown=emails.orders.shipped
1php artisan make:mail OrderShipped --markdown=emails.orders.shipped
接著,在 build
方法內設定 Mailable 時,不呼叫 view
方法,而是改呼叫 markdown
方法。makrdown
方法接受 Markdown 樣板的名稱,以及一組用來提供給樣板的可選資料陣列:
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{9 ->markdown('emails.orders.shipped', [10 'url' => $this->orderUrl,11 ]);12}
1/**2 * Build the message.3 *4 * @return $this5 */6public function build()7{9 ->markdown('emails.orders.shipped', [10 'url' => $this->orderUrl,11 ]);12}
撰寫 Markdown 訊息
Markdown 的 Markdown 使用 Blade 元件與 Markdown 格式的組合,讓我們能輕鬆地使用 Laravel 內建的 E-Mail UI 元件來建立訊息:
1@component('mail::message')2# Order Shipped34Your order has been shipped!56@component('mail::button', ['url' => $url])7View Order8@endcomponent910Thanks,<br>11{{ config('app.name') }}12@endcomponent
1@component('mail::message')2# Order Shipped34Your order has been shipped!56@component('mail::button', ['url' => $url])7View Order8@endcomponent910Thanks,<br>11{{ config('app.name') }}12@endcomponent
在撰寫 Markdown 郵件時請不要增加縮排。依據 Markdown 標準,Markdown 解析程式會將縮排的內容轉譯為程式碼區塊。
Button 元件
Button 元件轉譯一個置中的按鈕連結。這個元件接受兩個引數,一個是 url
網址,另一個則是可選的 color
顏色。支援的顏色有 primary
、success
、error
。在訊息中可以加上不限數量的 Button 元件:
1@component('mail::button', ['url' => $url, 'color' => 'success'])2View Order3@endcomponent
1@component('mail::button', ['url' => $url, 'color' => 'success'])2View Order3@endcomponent
Panel 元件
Panel 元件將給定的文字區塊轉譯在一個面板中,面板的底色與訊息中其他部分的背景色稍有不同。我們可以使用 Panel 元件來讓給定區塊的文字較為醒目:
1@component('mail::panel')2This is the panel content.3@endcomponent
1@component('mail::panel')2This is the panel content.3@endcomponent
Table 元件
Table 元件可讓我們將 Markdown 表格轉為 HTML 表格。該元件接受一個 Markdown 表格作為其內容。支援使用預設的 Markdown 表格對其格式來對其表格欄位:
1@component('mail::table')2| Laravel | Table | Example |3| ------------- |:-------------:| --------:|4| Col 2 is | Centered | $10 |5| Col 3 is | Right-Aligned | $20 |6@endcomponent
1@component('mail::table')2| Laravel | Table | Example |3| ------------- |:-------------:| --------:|4| Col 2 is | Centered | $10 |5| Col 3 is | Right-Aligned | $20 |6@endcomponent
自訂元件
可以將所有的 Markdown 郵件元件匯出到專案內來自訂這些元件。若要匯出元件,請使用 vendor:publish
Artisan 指令來安裝 laravel-mail
素材標籤:
1php artisan vendor:publish --tag=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 元件製作一個全新的主題,可在 html/themes
目錄下放置一個 CSS 檔。命名好 CSS 檔並保存後,請修改專案 config/mail.php
設定檔中的 theme
選項為該新主題的名稱:
若要為個別 Mailable 自訂主題,可在 Mailable 類別上將 $theme
屬性設為傳送該 Mailable 時要使用的主題名稱:
傳送郵件
若要傳送郵件,請使用 Mail
Facade上的
to方法。可傳入電子郵件位址、使用者實體、或是一組包含使用者的 Collection 給
to方法。若傳入物件或一組包含物件的 Collection,則 Mailer 在判斷收件人時會自動使用這些物件的
email與
name屬性來判斷。因此,請確認這些物件上是否有這兩個屬性。指定好收件人後,就可傳入 Mailable 類別的實體給
send` 方法:
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Mail\OrderShipped;7use App\Models\Order;8use Illuminate\Http\Request;9use Illuminate\Support\Facades\Mail;1011class OrderShipmentController extends Controller12{13 /**14 * Ship the given order.15 *16 * @param \Illuminate\Http\Request $request17 * @return \Illuminate\Http\Response18 */19 public function store(Request $request)20 {21 $order = Order::findOrFail($request->order_id);2223 // Ship the order...2425 Mail::to($request->user())->send(new OrderShipped($order));26 }27}
1<?php23namespace App\Http\Controllers;45use App\Http\Controllers\Controller;6use App\Mail\OrderShipped;7use App\Models\Order;8use Illuminate\Http\Request;9use Illuminate\Support\Facades\Mail;1011class OrderShipmentController extends Controller12{13 /**14 * Ship the given order.15 *16 * @param \Illuminate\Http\Request $request17 * @return \Illuminate\Http\Response18 */19 public function store(Request $request)20 {21 $order = Order::findOrFail($request->order_id);2223 // Ship the order...2425 Mail::to($request->user())->send(new OrderShipped($order));26 }27}
傳送訊息時,除了「to」方法能用來指定收件人外,還可以指定「CC」與「BCC」收件人。可將「to」、「cc」、「bcc」等方法串聯使用,以指定這些方法對應的收件人:
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->send(new OrderShipped($order));
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->send(new OrderShipped($order));
在收件人中迴圈
有時候,我們會需要迭代一組收件人或 E-Mail 位址的陣列來將 Mailable 傳送給多個收件人。不過,因為 to
方法會將 E-Mail 位址加到 Mailable 的收件人列表上,因此每次循環都會將該郵件再傳送給之前的收件人一次。所以,每個收件人都需要重新建立一個新的 Mailable 實體:
2 Mail::to($recipient)->send(new OrderShipped($order));3}
2 Mail::to($recipient)->send(new OrderShipped($order));3}
使用指定的 Mailer 來傳送郵件
預設情況下,Laravel 會使用專案 mail
設定中設為 default
的 Mailaer 來寄送郵件。不過,也可以使用 mailer
方法來特定的 Mailer 設定傳送訊息:
1Mail::mailer('postmark')2 ->to($request->user())3 ->send(new OrderShipped($order));
1Mail::mailer('postmark')2 ->to($request->user())3 ->send(new OrderShipped($order));
將郵件放入佇列
將郵件訊息放入佇列
由於傳送郵件訊息可能對程式的 Response 時間造成負面影響,因此許多開發人員都選擇將郵件訊息放入陣列來在背景執行。在 Laravel 中,使用內建的統一佇列 API,就能輕鬆地將郵件放入佇列。若要將郵件訊息放入佇列,請在指定好收件人後使用 Mail
Facade 的 queue
方法:
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->queue(new OrderShipped($order));
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()->addMinutes(10), new OrderShipped($order));
1Mail::to($request->user())2 ->cc($moreUsers)3 ->bcc($evenMoreUsers)4 ->later(now()->addMinutes(10), new OrderShipped($order));
推入指定的佇列
由於所有使用 make:mail
指令產生的 Mailable 類別都使用 Illiminate\Bus\Queuable
Trait,因此我們可以在任何一個 Mailable 類別實體上呼叫 onQueue
與 onConnection
方法,可讓我們指定該訊息要使用的佇列名稱:
1$message = (new OrderShipped($order))2 ->onConnection('sqs')3 ->onQueue('emails');45Mail::to($request->user())6 ->cc($moreUsers)7 ->bcc($evenMoreUsers)8 ->queue($message);
1$message = (new OrderShipped($order))2 ->onConnection('sqs')3 ->onQueue('emails');45Mail::to($request->user())6 ->cc($moreUsers)7 ->bcc($evenMoreUsers)8 ->queue($message);
預設佇列
若有想要永遠放入佇列的 Mailable 類別,可在該類別上實作 ShouldQueue
Contract。接著,即使使用 send
方法來寄送郵件,由於該 Mailable 有實作 ShouldQueue
Contract,因此還是會被放入佇列:
1use Illuminate\Contracts\Queue\ShouldQueue;23class OrderShipped extends Mailable implements ShouldQueue4{5 //6}
1use Illuminate\Contracts\Queue\ShouldQueue;23class OrderShipped extends Mailable implements ShouldQueue4{5 //6}
佇列的 Mailable 與資料庫 Transaction
當佇列 Mailable 是在資料庫 Transaction 內分派的時候,這個 Mailable 可能會在資料庫 Transaction 被 Commit 前就被佇列進行處理了。發生這種情況時,在資料庫 Transaction 期間對 Model 或資料庫記錄所做出的更新可能都還未反應到資料庫內。另外,所有在 Transaction 期間新增的 Model 或資料庫記錄也可能還未出現在資料庫內。若 Mailable 有使用這些 Model 的話,在處理該佇列 Mailable 的任務時可能會出現未預期的錯誤。
若佇列的 after_commit
選項設為 false
,則我們還是可以通過在寄送郵件訊息前呼叫 afterCommit
方法來表示出該 Mailable 應在所有資料庫 Transaction 都被 Commit 後才分派:
1Mail::to($request->user())->send(2 (new OrderShipped($order))->afterCommit()3);
1Mail::to($request->user())->send(2 (new OrderShipped($order))->afterCommit()3);
或者,也可以在 Mailable 的 Constructor 上呼叫 afterCommit
方法:
1<?php23namespace App\Mail;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Mail\Mailable;8use Illuminate\Queue\SerializesModels;910class OrderShipped extends Mailable implements ShouldQueue11{12 use Queueable, SerializesModels;1314 /**15 * Create a new message instance.16 *17 * @return void18 */19 public function __construct()20 {21 $this->afterCommit();22 }23}
1<?php23namespace App\Mail;45use Illuminate\Bus\Queueable;6use Illuminate\Contracts\Queue\ShouldQueue;7use Illuminate\Mail\Mailable;8use Illuminate\Queue\SerializesModels;910class OrderShipped extends Mailable implements ShouldQueue11{12 use Queueable, SerializesModels;1314 /**15 * Create a new message instance.16 *17 * @return void18 */19 public function __construct()20 {21 $this->afterCommit();22 }23}
要瞭解更多有關這類問題的解決方法,請參考有關佇列任務與資料庫 Transaction 有關的說明文件。
轉譯 Mailable
有時候我們會想在不寄送 Mailable 的情況下截取其 HTML 內容。若要截取其內容,可呼叫 Mailable 的 render
方法。該方法會以字串回傳該 Mailable 的 HTML 取值內容:
1use App\Mail\InvoicePaid;2use App\Models\Invoice;34$invoice = Invoice::find(1);56return (new InvoicePaid($invoice))->render();
1use App\Mail\InvoicePaid;2use App\Models\Invoice;34$invoice = Invoice::find(1);56return (new InvoicePaid($invoice))->render();
在瀏覽器內預覽 Mailable
在設計 Mailable 樣板時,若能像普通的 Blade 樣板一樣在瀏覽器中預覽轉譯後的 Mailable 該有多方便。因為這樣,在 Laravel 中,可以直接在 Route 閉包或 Controller 中回傳任何的 Mailable。若回傳 Mailable,則會轉譯該 Mailable 並顯示在瀏覽器上,讓我們不需將其寄到真實的電子郵件上也能快速檢視其設計:
1Route::get('/mailable', function () {2 $invoice = App\Models\Invoice::find(1);34 return new App\Mail\InvoicePaid($invoice);5});
1Route::get('/mailable', function () {2 $invoice = App\Models\Invoice::find(1);34 return new App\Mail\InvoicePaid($invoice);5});
本土化 Mailable
在 Laravel 中,可以使用與 Request 中不同的語系設定來傳送郵件,且在郵件被放入佇列後依然會使用所設定的語系。
若要設定語系,請使用 Mail
Facade 提供的 locale
方法來設定要使用的語言。在轉譯 Mailable 樣板時,程式會先進入這個語系中,轉譯完畢後再回到之前的語系:
1Mail::to($request->user())->locale('es')->send(2 new OrderShipped($order)3);
1Mail::to($request->user())->locale('es')->send(2 new OrderShipped($order)3);
使用者偏好的語系
有時候,我們的程式會儲存每個使用者偏好的語言。只要在一個或多個 Model 上實作 HasLocalePreference
Contract,就可以讓 Laravel 在寄送郵件時使用這些儲存的語系:
1use Illuminate\Contracts\Translation\HasLocalePreference;23class User extends Model implements HasLocalePreference4{5 /**6 * Get the user's preferred locale.7 *8 * @return string9 */10 public function preferredLocale()11 {12 return $this->locale;13 }14}
1use Illuminate\Contracts\Translation\HasLocalePreference;23class User extends Model implements HasLocalePreference4{5 /**6 * Get the user's preferred locale.7 *8 * @return string9 */10 public function preferredLocale()11 {12 return $this->locale;13 }14}
實作好該介面後,向該 Model 寄送 Mailable 或通知時,Laravel 會自動使用偏好的語系。因此,使用該介面時不需呼叫 locale
方法:
1Mail::to($request->user())->send(new OrderShipped($order));
1Mail::to($request->user())->send(new OrderShipped($order));
測試 Mailable
Laravel 提供了多種可測試 Mailable 是否包含於其內容的方便方法。這些方法是:assertSeeInHtml
、assertDontSeeInHtml
、assertSeeInText
、assertDontSeeInText
。
就和預期的一樣,有「HTML」的^ Assertion 判斷 HTML 版本的 Mailable 是否包含給定字串,而「Text」版本的 Assertion 則判斷純文字版本的 Mailable 是否包含給定字串:
1use App\Mail\InvoicePaid;2use App\Models\User;34public function test_mailable_content()5{6 $user = User::factory()->create();78 $mailable = new InvoicePaid($user);910 $mailable->assertSeeInHtml($user->email);11 $mailable->assertSeeInHtml('Invoice Paid');1213 $mailable->assertSeeInText($user->email);14 $mailable->assertSeeInText('Invoice Paid');15}
1use App\Mail\InvoicePaid;2use App\Models\User;34public function test_mailable_content()5{6 $user = User::factory()->create();78 $mailable = new InvoicePaid($user);910 $mailable->assertSeeInHtml($user->email);11 $mailable->assertSeeInHtml('Invoice Paid');1213 $mailable->assertSeeInText($user->email);14 $mailable->assertSeeInText('Invoice Paid');15}
測試 Mailable 的寄送
在測試郵件是否有寄給特定使用者時,我們建議與 Mailable 的內容分開測試。若要瞭解如何測試郵件是否有寄出,請參考有關 Mail 模擬的說明文件。
郵件與本機開發
在開發有寄送郵件的程式時,我們通常都不會想實際將郵件寄到真實的 E-Mail 位址上。Laravel 提供了數種數種方法來在本機上開發時「禁用」郵件的實際傳送。
Log Driver
log
郵件 Driver 不會實際寄送電子郵件,而是將所有電子郵件訊息寫入日誌檔以供檢查。一般來說,Log Driver 只會在開發環境上使用。有關一找不同環境設定專案的方法,請參考設定的說明文件。
HELO / Mailtrap / MailHog
或者,也可以使用如 HELO 或 Mailtrap 這類服務搭配 smtp
Driver 來將電子郵件寄送到一個「模擬的」收件夾,並像在真的郵件用戶端一樣檢視這些郵件。這種做法的好處就是可以在 Mailtrap 的訊息檢視工具中實際檢視寄出的郵件。
若使用 Laravel Sail,,則可使用 MailHog 來預覽訊息。當 Sail 有在執行時,可在 http://localhost:8025
上存取 MailHog 的界面。
使用全域的 to
位址
最後一種方法,就是我們可以叫用 Mail
Facade 提供的 alwaysTo
方法指定一個全域的「to」位址。一般來說,應在專案的其中一個 Service Provider 內 boot
方法中呼叫這個方法:
1use Illuminate\Support\Facades\Mail;23/**4 * Bootstrap any application services.5 *6 * @return void7 */8public function boot()9{10 if ($this->app->environment('local')) {12 }13}
1use Illuminate\Support\Facades\Mail;23/**4 * Bootstrap any application services.5 *6 * @return void7 */8public function boot()9{10 if ($this->app->environment('local')) {12 }13}
事件
在處理郵件訊息寄送時,Laravel 會觸發兩個事件。MessageSending
事件會在寄出郵件前觸發,而MessageSent
事件則會在訊息寄出後觸發。請記得,這些事件都是在 寄送 郵件的時候出發的,而不是在放入佇列時觸發。可以在 App\Providers\EventServiceProvider
Service Provider 上為這些 Event 註冊 Listener:
1/**2 * The event listener mappings for the application.3 *4 * @var array5 */6protected $listen = [7 'Illuminate\Mail\Events\MessageSending' => [8 'App\Listeners\LogSendingMessage',9 ],10 'Illuminate\Mail\Events\MessageSent' => [11 'App\Listeners\LogSentMessage',12 ],13];
1/**2 * The event listener mappings for the application.3 *4 * @var array5 */6protected $listen = [7 'Illuminate\Mail\Events\MessageSending' => [8 'App\Listeners\LogSendingMessage',9 ],10 'Illuminate\Mail\Events\MessageSent' => [11 'App\Listeners\LogSentMessage',12 ],13];