> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 郵件寄送

> 從 Laravel Mailable 類別的基本，到 Markdown 郵件與佇列寄送。

## Laravel 的郵件功能是什麼

Laravel 的郵件功能建立在 [Symfony Mailer](https://symfony.com/doc/current/mailer.html) 之上，支援 SMTP、Mailgun、Postmark、Resend、Amazon SES、sendmail 等驅動。

<Info>
  以往 Laravel 使用的 SwiftMailer 已被廢棄。Laravel 9 以後改用 Symfony Mailer。
</Info>

郵件設定於 `config/mail.php` 管理。可透過 `.env` 切換使用的驅動。

```ini theme={null}
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
```

## 建立 Mailable 類別

在 Laravel 中，每封應用程式寄出的信會以「Mailable」類別表達。可用 `make:mail` Artisan 指令建立。

```shell theme={null}
php artisan make:mail OrderShipped
```

產生的類別會放到 `app/Mail/` 目錄。

## Mailable 設定

產生的 Mailable 類別會有 `envelope()`、`content()`、`attachments()` 3 個方法。

```php theme={null}
<?php

namespace App\Mail;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: '訂單已出貨',
        );
    }

    public function content(): Content
    {
        return new Content(
            view: 'mail.orders.shipped',
        );
    }

    public function attachments(): array
    {
        return [];
    }
}
```

### 設定寄件者

於 `envelope()` 指定寄件者。

```php theme={null}
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;

public function envelope(): Envelope
{
    return new Envelope(
        from: new Address('shop@example.com', 'Shop Support'),
        subject: '訂單已出貨',
    );
}
```

若在 `config/mail.php` 設定全域寄件者，個別 Mailable 就可省略。

```php theme={null}
'from' => [
    'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
    'name' => env('MAIL_FROM_NAME', 'Example'),
],
```

### 設定郵件內容

在 `content()` 指定 Blade 樣板。建構子中設定為 `public` 屬性的資料會自動可在樣板中使用。

```php theme={null}
public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
    );
}
```

建立對應的 Blade 樣板（`resources/views/mail/orders/shipped.blade.php`）：

```blade theme={null}
<!DOCTYPE html>
<html>
<body>
    <h1>您的訂單已出貨</h1>
    <p>訂單編號：{{ $order->id }}</p>
    <p>總金額：¥{{ number_format($order->total) }}</p>
    <p>感謝您的訂購。</p>
</body>
</html>
```

也可以透過 `with` 明確傳入資料。此時屬性可設為 `protected` 或 `private`。

```php theme={null}
public function __construct(
    protected Order $order,
) {}

public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
        with: [
            'orderId' => $this->order->id,
            'total' => $this->order->total,
        ],
    );
}
```

## 寄送郵件

用 `Mail` Facade 的 `to()` 指定收件者，`send()` 寄送。

```mermaid theme={null}
flowchart TD
    A["Mail::to()->send(new OrderShipped())"] --> B{"實作 ShouldQueue？"}
    B -- "No" --> C["同步寄送<br>送到 Mailer"]
    B -- "Yes" --> D["進入佇列<br>背景寄送"]
    D --> C
    C --> E{"MAIL 驅動"}
    E --> F["smtp"]
    E --> G["ses / mailgun<br>/ postmark"]
    E --> H["log<br>(開發用)"]
```

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class OrderShipmentController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $order = Order::findOrFail($request->order_id);

        Mail::to($request->user())->send(new OrderShipped($order));

        return redirect('/orders');
    }
}
```

可指定 CC / BCC。

```php theme={null}
Mail::to($request->user())
    ->cc($ccUsers)
    ->bcc($bccUsers)
    ->send(new OrderShipped($order));
```

用特定 mailer 寄送可使用 `mailer()`：

```php theme={null}
Mail::mailer('postmark')
    ->to($request->user())
    ->send(new OrderShipped($order));
```

## 使用佇列寄送郵件

郵件寄送會影響回應時間，建議透過佇列在背景寄送。

```php theme={null}
Mail::to($request->user())->queue(new OrderShipped($order));
```

<Warning>
  使用佇列前需先設定佇列並啟動 worker。
</Warning>

也可以延遲寄送：

```php theme={null}
Mail::to($request->user())
    ->later(now()->plus(minutes: 10), new OrderShipped($order));
```

在 Mailable 類別實作 `ShouldQueue`，即使呼叫 `send()` 也一定會進入佇列。

```php theme={null}
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderShipped extends Mailable implements ShouldQueue
{
    // ...
}
```

### 用 PHP 屬性指定佇列連線與佇列名稱

可用 PHP 屬性直接於 Mailable 上指定佇列連線與名稱，取代 `on()->queue()` 方法鏈，以類別層級宣告式設定。

```php theme={null}
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;

#[Connection('sqs')]
#[Queue('emails')]
class OrderShipped extends Mailable implements ShouldQueue
{
    // ...
}
```

如此一來，可在類別定義上明確表示此 Mailable 永遠會送到 SQS 連線的 `emails` 佇列。

## 建立 Markdown Mail

使用 Markdown 格式的郵件，可運用 Laravel 提供的漂亮響應式 HTML 樣板。

### 產生 Markdown Mailable

以 `--markdown` 選項產生 Mailable。

```shell theme={null}
php artisan make:mail OrderShipped --markdown=mail.orders.shipped
```

於 `content()` 使用 `markdown` 參數：

```php theme={null}
use Illuminate\Mail\Mailables\Content;

public function content(): Content
{
    return new Content(
        markdown: 'mail.orders.shipped',
        with: [
            'url' => route('orders.show', $this->order),
        ],
    );
}
```

### 撰寫 Markdown 樣板

以 Laravel 提供的 Blade 元件撰寫內容。

```blade theme={null}
<x-mail::message>
# 您的訂單已出貨

我們通知您訂單已出貨。

<x-mail::button :url="$url">
查看訂單
</x-mail::button>

感謝您的支持。<br>
{{ config('app.name') }}
</x-mail::message>
```

可用的元件：

| 元件                 | 說明                                            |
| ------------------ | --------------------------------------------- |
| `<x-mail::button>` | 按鈕連結（`color` 可指定 `primary`、`success`、`error`） |
| `<x-mail::panel>`  | 醒目面板                                          |
| `<x-mail::table>`  | Markdown 格式的表格                                |

<Tip>
  Markdown 郵件會同時自動產生純文字版本。即便使用不支援 HTML 的信箱也能讀取。
</Tip>

## 郵件預覽

從路由回傳 Mailable 實例即可在瀏覽器預覽。適合開發期確認。

```php theme={null}
// routes/web.php
Route::get('/mailable', function () {
    $order = App\Models\Order::first();
    return new App\Mail\OrderShipped($order);
});
```

## 本機開發時的郵件

為避免在本機不小心寄送到正式環境的信箱，可以使用 [Mailpit](https://github.com/axllent/mailpit) 等工具。

```ini theme={null}
# .env (Mailpit)
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
```

`Laragon` 或 `Laravel Herd` 皆預設內含 Mailpit。

<Info>
  使用 `log` 驅動時，郵件內容會記錄到日誌檔。若只是想確認寄信這是最方便的方式。
</Info>

```ini theme={null}
MAIL_MAILER=log
```

## 總結

| 想要做的事          | 方法                                |
| -------------- | --------------------------------- |
| 建立 Mailable    | `php artisan make:mail ClassName` |
| 設定寄件者          | `envelope()` 的 `from` 參數          |
| 使用 Blade 樣板    | `content()` 的 `view` 參數           |
| 使用 Markdown 樣板 | `content()` 的 `markdown` 參數       |
| 寄送郵件           | `Mail::to()->send()`              |
| 用佇列寄送          | `Mail::to()->queue()`             |
| 延遲寄送           | `Mail::to()->later()`             |


## Related topics

- [任務排程](/zh-TW/scheduling.md)
- [教學 - Laravel Console Starter](/zh-TW/packages/laravel-console-starter/tutorial.md)
- [從 Laravel 8 升級到 9](/zh-TW/blog/upgrade-8-to-9.md)
- [電子郵件驗證（Email Verification）](/zh-TW/verification.md)
- [密碼重設](/zh-TW/passwords.md)
