跳轉到主要內容
想從套件提供自訂的郵件寄送方式時,請將建立 Symfony Transport 的處理註冊到 Laravel。不需要重新實作 Mailable 或 Laravel 的 Mailer。 本頁確認了 Laravel Framework v13.39.0 的實作。基本的寄送方式請參閱寄送郵件,提供者的準備請參閱套件開發。

區分 Mailer 名稱與 Transport 名稱

Mail::mailer('receipts') 會讀取 mail.mailers.receipts,並呼叫對應 transport 的建立處理。MailManager::build() 會將該 Transport 交給 Laravel 的 Mailer,在一般的具名解析中也會設定全域寄件者等資訊。 自訂建立處理會比內建 Transport 更優先被選用。請不要覆寫 smtp 或 ses,而是使用套件專屬的 Transport 名稱。

在不對外寄送的情況下建立註冊路徑

首先要確認的不是投遞目標的 API,而是設定與解析路徑。在 Acme\Postbox 套件的 src/Transport/MemoryTransport.php 中,建立一個繼承標準 ArrayTransport 的類別。
ArrayTransport::send() 會將 Symfony 的 SentMessage 儲存在內部的 Collection 中。可以用 messages() 確認,並用 flush() 清除該實例的紀錄。標籤是用來確認設定已傳達到建立處理。
此範例不會將郵件投遞到外部,僅供學習註冊與測試之用。記憶體中的紀錄不會跨行程共享或持久化,也無法確認是否送達收件者。若只是想記錄在記憶體中,使用內建的 array Transport 就足夠了。

註冊到延遲載入的 Manager

官方文件是在 boot() 中呼叫 Mail::extend()。在套件中,為了避免在不使用郵件的應用程式中多餘地建立 Manager,可以搭配服務解析後的掛鉤。 建立 src/PostboxServiceProvider.php。
提供者可透過套件自動探索等方式載入。
  • Laravel 的 MailServiceProvider 是延遲提供者,會將 mail.manager 註冊為 singleton。Mail Facade 也會解析這個鍵。
  • callAfterResolving() 在尚未解析時會等待之後的解析,對已解析的 Manager 則會立即套用擴充。它並不會替換已建立的 Mailer。
  • 請避免把註冊擴充的自有提供者也單純設為 DeferrableProvider。要確保在掛鉤註冊之前不會有人要求自訂 Transport。
  • 此範例在自己的 boot() 中註冊掛鉤。若其他提供者在此之前就使用自訂 Mailer,這種啟動順序並不受保證。請設計好註冊順序,且不要在 register() 中寄送郵件。

不要將 extend 的引數與其他 Manager 混淆

MailManager::createSymfonyTransport() 只會以單一設定陣列呼叫自訂建立處理。它不像 Cache::extend() 那樣接收應用程式與設定兩個引數。回傳值是實作 TransportInterface 的 Transport,而不是 Laravel 的 Mailer 或寄送結果。 此外,MailManager::extend() 只會保留閉包,不會將其重新綁定到 Manager。範例中使用 static 閉包與明確的引數,避免依賴 $this 的解讀方式。 若需要外部 API 的用戶端,請在這個建立處理中套用設定並傳給 Transport。設計上不要在註冊時就寄送,且在多個 Mailer 使用同一個 Transport 名稱時,不要讓設定或投遞目標混在一起。

在使用端應用程式中新增 Mailer

在 config/mail.php 既有的 mailers 陣列中新增以下兩項。套件端不要覆寫使用者的預設值或整個 mailers。
若想設為預設值,要在 MAIL_MAILER 中指定的是 receipts 這個 Mailer 名稱,而不是 Transport 名稱。若個別指定,就能在不變更既有預設值的情況下使用。
這裡的 raw() 是不準備視圖、用來確認同步寄送路徑的範例。使用 Mailable 的一般寄送也一樣,Mailer 產生內容等之後會交給同一個 Transport。

建立實際投遞用 Transport 時的界線

在投遞到外部服務的實作中,可依照官方文件繼承 Symfony\Component\Mailer\Transport\AbstractTransport,並實作 doSend(SentMessage $message): void 與 __toString(): string。上面的註冊方式可以直接沿用。 Laravel 的 Mailer::sendSymfonyMessage() 會呼叫 Transport 的 send()。當 MessageSending 監聽器中止寄送,或 Transport 回傳 null 時,Laravel 的 send() 也不會回傳寄送結果。請不要只以沒有拋出例外來判定投遞成功。 即使回傳了 SentMessage,也不保證已送達收件者的信箱。

區分設定變更與 Mailer 的重新解析

MailManager 會依 Mailer 名稱保留 Mailer 及其 Transport。僅變更設定或重新註冊 extend(),並不會更新已建立的物件。
$same 與 $before 是同一個 Mailer。在 purge('receipts') 之後取得的 $after,則會依新的設定建立 Transport。 經確認,purge() 的實作只是從 Manager 內部的陣列中移除參照。舊的 $before,以及已注入該 Mailer 的服務都不會被替換。它也不是明確關閉外部連線的處理。

failover 等父 Mailer 也會另外保留

failover 與 roundrobin 會讀取子 Mailer 名稱的設定,直接建立 Transport 並組入父 Transport 中。這並不是共用子 Mail::mailer() 實例的機制。 因此,即使變更子設定並呼叫 purge('receipts'),已解析的父 Mailer 內部的 Transport 也不會更新。必須連同要更新的父 Mailer 一起重新解析。即使使用 forgetMailers(),也無法替換使用端已持有的父 Mailer 參照。 在正式環境中,請將設定快取的更新與長時間運行 Worker 的重新啟動納入一般的部署流程。不要把重新解析視為一次更新所有行程,也請參閱服務的生命週期。

不只用 Mail::fake,也要測試實際的建立路徑

Mail::fake() 是用來記錄 Mailable 的寄送與佇列登錄。MailFake::mailer() 不會解析實際的 Mailer,而是記錄 Mailer 名稱並回傳 fake 本身。raw() 也不會執行寄送處理。即使沒有註冊自訂 Transport,Mailable 的寄送斷言也可能成功。 在套件的測試環境中,如下所示不使用 fake,讓流程經過實際的 Manager 與 Transport。此範例的 Transport 不會對外寄送,因此能在沒有實際投遞風險的情況下確認註冊路徑。這裡會先載入輔助提供者,也驗證 Manager 已解析的情況。
這個測試確認的是註冊、設定、同步寄送與已保留物件的界線。對於實際投遞用的 Transport,請另外透過 API 用戶端的 mock 或 fake 驗證酬載與失敗回應。若提供佇列寄送,也請將佇列的測試分開,並確認 Worker 端同樣會載入提供者。

參考的一手資料

最後修改於 2026年10月30日