> ## 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` 파사드의 `to()` 메서드로 수신자를 지정하고, `send()`로 메일을 전송합니다.

```mermaid theme={null}
flowchart TD
    A["Mail::to()->send(new OrderShipped())"] --> B{"ShouldQueue<br>구현?"}
    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()` 메서드를 사용합니다.

```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>
  큐를 사용하려면 사전에 큐의 설정과 워커의 기동이 필요합니다.
</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

- [이벤트와 리스너](/ko/events.md)
- [컬렉션](/ko/collections.md)
- [Contracts(계약)](/ko/contracts.md)
- [이메일 인증(Email Verification)](/ko/verification.md)
- [비밀번호 재설정](/ko/passwords.md)
