메일
업데이트됨번역일: 2026년 11월 7일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 11월 4일
- 번역 갱신
- 2026년 11월 7일
메일
- 소개
- Mailable 생성하기
- Mailable 작성하기
- Markdown Mailable
- 메일 전송하기
- Mailable 렌더링하기
- Mailable 현지화하기
- 테스트
- 메일과 로컬 개발 환경
- 이벤트
- 커스텀 트랜스포트
소개
이메일 발송이 복잡할 필요는 없습니다. Laravel은 널리 쓰이는 Symfony Mailer 컴포넌트를 기반으로 한 깔끔하고 단순한 이메일 API를 제공합니다. Laravel과 Symfony Mailer는 SMTP, Cloudflare, Mailgun, Postmark, Resend, Amazon SES, sendmail을 통한 이메일 발송 드라이버를 제공하므로, 로컬 환경이든 클라우드 기반 서비스든 원하는 방식으로 빠르게 메일 발송을 시작할 수 있습니다.
설정
Laravel의 이메일 서비스는 애플리케이션의 config/mail.php 설정 파일에서 구성합니다. 이 파일에 정의된 각 메일러(mailer)는 고유한 설정은 물론 고유한 "트랜스포트(transport)"를 가질 수 있으므로, 이메일 종류에 따라 서로 다른 이메일 서비스를 사용할 수 있습니다. 예를 들어 트랜잭션 메일(주문 확인, 비밀번호 재설정 등)은 Postmark로, 대량 메일은 Amazon SES로 발송하도록 구성할 수 있습니다.
mail 설정 파일에는 mailers 설정 배열이 있습니다. 이 배열에는 Laravel이 지원하는 주요 메일 드라이버/트랜스포트별 샘플 설정이 하나씩 들어 있으며, default 설정값은 애플리케이션이 이메일을 보낼 때 기본으로 사용할 메일러를 결정합니다.
드라이버 / 트랜스포트 사전 준비
Mailgun, Postmark, Resend 같은 API 기반 드라이버는 SMTP 서버를 통해 메일을 보내는 것보다 대체로 더 간단하고 빠릅니다. 가능하면 이러한 드라이버 중 하나를 사용하기를 권장합니다.
Cloudflare 드라이버
Cloudflare 드라이버를 사용하려면 Composer로 Symfony의 HTTP Client를 설치합니다.
composer require symfony/http-client다음으로 애플리케이션의 config/mail.php 설정 파일을 두 군데 수정해야 합니다. 먼저 기본 메일러를 cloudflare로 지정합니다.
'default' => env('MAIL_MAILER', 'cloudflare'),그런 다음 mailers 배열에 아래 설정 배열을 추가합니다.
'cloudflare' => [
'transport' => 'cloudflare',
],기본 메일러를 설정했다면, config/services.php 설정 파일에 다음 옵션을 추가합니다.
'cloudflare' => [
'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
'key' => env('CLOUDFLARE_KEY'),
],Mailgun 드라이버
Mailgun 드라이버를 사용하려면 Composer로 Symfony의 Mailgun Mailer 트랜스포트를 설치합니다.
composer require symfony/mailgun-mailer symfony/http-client다음으로 애플리케이션의 config/mail.php 설정 파일을 두 군데 수정해야 합니다. 먼저 기본 메일러를 mailgun으로 지정합니다.
'default' => env('MAIL_MAILER', 'mailgun'),그런 다음 mailers 배열에 아래 설정 배열을 추가합니다.
'mailgun' => [
'transport' => 'mailgun',
// 'client' => [
// 'timeout' => 5,
// ],
],기본 메일러를 설정했다면, config/services.php 설정 파일에 다음 옵션을 추가합니다.
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
],미국 Mailgun 리전을 사용하지 않는 경우(예: 유럽 리전), services 설정 파일에서 해당 리전의 엔드포인트를 지정할 수 있습니다.
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),
'scheme' => 'https',
],Postmark 드라이버
Postmark 드라이버를 사용하려면 Composer로 Symfony의 Postmark Mailer 트랜스포트를 설치합니다.
composer require symfony/postmark-mailer symfony/http-client다음으로 애플리케이션의 config/mail.php 설정 파일에서 default 옵션을 postmark로 지정합니다. 기본 메일러를 설정했다면 config/services.php 설정 파일에 다음 옵션이 있는지 확인합니다.
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],특정 메일러가 사용할 Postmark 메시지 스트림을 지정하고 싶다면, 해당 메일러의 설정 배열에 message_stream_id 옵션을 추가하면 됩니다. 이 설정 배열은 애플리케이션의 config/mail.php 설정 파일에서 찾을 수 있습니다.
'postmark' => [
'transport' => 'postmark',
'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],이 방식을 사용하면 서로 다른 메시지 스트림을 가진 Postmark 메일러를 여러 개 구성할 수도 있습니다.
Resend 드라이버
Resend 드라이버를 사용하려면 Composer로 Resend의 PHP SDK를 설치합니다.
composer require resend/resend-php다음으로 애플리케이션의 config/mail.php 설정 파일에서 default 옵션을 resend로 지정합니다. 기본 메일러를 설정했다면 config/services.php 설정 파일에 다음 옵션이 있는지 확인합니다.
'resend' => [
'key' => env('RESEND_API_KEY'),
],SES 드라이버
Amazon SES 드라이버를 사용하려면 먼저 Amazon AWS SDK for PHP를 설치해야 합니다. Composer 패키지 관리자로 설치할 수 있습니다.
composer require aws/aws-sdk-php다음으로 config/mail.php 설정 파일의 default 옵션을 ses로 지정하고, config/services.php 설정 파일에 다음 옵션이 있는지 확인합니다.
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],세션 토큰을 이용해 AWS 임시 자격 증명을 사용하려면, 애플리케이션의 SES 설정에 token 키를 추가하면 됩니다.
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'token' => env('AWS_SESSION_TOKEN'),
],SES의 구독 관리 기능을 사용하려면, 메일 메시지의 headers 메서드가 반환하는 배열에 X-Ses-List-Management-Options 헤더를 포함하면 됩니다.
/**
* Get the message headers.
*/
public function headers(): Headers
{
return new Headers(
text: [
'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic',
],
);
}SES 테넌트를 통해 이메일을 보내려면 headers 메서드에서 X-Ses-Tenant-Name 헤더를 반환하면 됩니다. Laravel은 메시지를 전송할 때 이 헤더 값을 SES의 TenantName 옵션으로 전달합니다.
public function headers(): Headers
{
return new Headers(
text: [
'X-Ses-Tenant-Name' => 'tenant-id',
],
);
}이메일을 전송할 때 Laravel이 AWS SDK의 SendEmail 메서드에 전달할 추가 옵션을 정의하고 싶다면, ses 설정 안에 options 배열을 정의하면 됩니다.
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'options' => [
'ConfigurationSetName' => 'MyConfigurationSet',
'EmailTags' => [
['Name' => 'foo', 'Value' => 'bar'],
],
],
],장애 조치(Failover) 설정
애플리케이션의 메일을 발송하도록 설정한 외부 서비스가 다운되는 경우가 있습니다. 이런 상황에 대비해, 기본 발송 드라이버에 장애가 생겼을 때 사용할 백업 메일 발송 설정을 하나 이상 정의해 두면 유용합니다.
이를 위해 애플리케이션의 mail 설정 파일에 failover 트랜스포트를 사용하는 메일러를 정의합니다. failover 메일러의 설정 배열에는 발송에 사용할 메일러를 선택하는 순서를 나타내는 mailers 배열이 들어 있어야 합니다.
'mailers' => [
'failover' => [
'transport' => 'failover',
'mailers' => [
'postmark',
'mailgun',
'sendmail',
],
'retry_after' => 60,
],
// ...
],failover 트랜스포트를 사용하는 메일러를 구성했다면, 장애 조치 기능을 사용하기 위해 애플리케이션의 .env 파일에서 이 메일러를 기본 메일러로 지정해야 합니다.
MAIL_MAILER=failover라운드 로빈 설정
roundrobin 트랜스포트를 사용하면 메일 발송 부하를 여러 메일러에 분산할 수 있습니다. 먼저 애플리케이션의 mail 설정 파일에 roundrobin 트랜스포트를 사용하는 메일러를 정의합니다. roundrobin 메일러의 설정 배열에는 발송에 사용할 메일러를 지정하는 mailers 배열이 들어 있어야 합니다.
'mailers' => [
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
// ...
],라운드 로빈 메일러를 정의했다면, 애플리케이션의 mail 설정 파일에서 default 설정 키의 값으로 이 메일러의 이름을 지정해 기본 메일러로 설정합니다.
'default' => env('MAIL_MAILER', 'roundrobin'),라운드 로빈 트랜스포트는 설정된 메일러 목록에서 무작위로 하나를 선택하고, 이후 이메일마다 다음 사용 가능한 메일러로 순차적으로 전환합니다. *고가용성(high availability)*을 목표로 하는 failover 트랜스포트와 달리, roundrobin 트랜스포트는 *로드 밸런싱(load balancing)*을 제공합니다.
Mailable 생성하기
Laravel 애플리케이션을 개발할 때, 애플리케이션이 보내는 각 유형의 이메일은 "mailable" 클래스로 표현됩니다. 이 클래스들은 app/Mail 디렉터리에 저장됩니다. 애플리케이션에 이 디렉터리가 보이지 않더라도 걱정하지 마세요. make:mail Artisan 명령어로 첫 번째 mailable 클래스를 생성하면 자동으로 만들어집니다.
php artisan make:mail OrderShippedMailable 작성하기
mailable 클래스를 생성했다면 파일을 열어 내용을 살펴보겠습니다. mailable 클래스의 설정은 envelope, content, attachments 메서드를 비롯한 여러 메서드에서 이루어집니다.
envelope 메서드는 메시지의 제목과, 경우에 따라 수신자를 정의하는 Illuminate\Mail\Mailables\Envelope 객체를 반환합니다. content 메서드는 메시지 내용을 생성할 때 사용할 Blade 템플릿을 정의하는 Illuminate\Mail\Mailables\Content 객체를 반환합니다.
발신자 설정하기
Envelope 사용하기
먼저 이메일의 발신자, 즉 이메일이 누구로부터 발송되는지(“from”)를 설정하는 방법을 살펴보겠습니다. 발신자를 설정하는 방법은 두 가지입니다. 첫 번째는 메시지의 envelope에 "from" 주소를 지정하는 방법입니다.
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
from: new Address('jeffrey@example.com', 'Jeffrey Way'),
subject: 'Order Shipped',
);
}필요하다면 replyTo 주소도 지정할 수 있습니다.
return new Envelope(
from: new Address('jeffrey@example.com', 'Jeffrey Way'),
replyTo: [
new Address('taylor@example.com', 'Taylor Otwell'),
],
subject: 'Order Shipped',
);전역 `from` 주소 사용하기
그러나 애플리케이션의 모든 이메일에서 같은 "from" 주소를 사용한다면, 생성하는 mailable 클래스마다 이를 추가하는 것이 번거로울 수 있습니다. 이럴 때는 config/mail.php 설정 파일에 전역 "from" 주소를 지정할 수 있습니다. mailable 클래스 안에서 다른 "from" 주소를 지정하지 않으면 이 주소가 사용됩니다.
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],또한 config/mail.php 설정 파일에 전역 "reply_to" 주소도 정의할 수 있습니다.
'reply_to' => [
'address' => 'example@example.com',
'name' => 'App Name',
],뷰 설정하기
mailable 클래스의 content 메서드 안에서 view, 즉 이메일 내용을 렌더링할 때 사용할 템플릿을 정의할 수 있습니다. 대부분의 이메일은 Blade 템플릿으로 내용을 렌더링하므로, 이메일의 HTML을 만들 때 Blade 템플릿 엔진의 모든 기능과 편리함을 그대로 활용할 수 있습니다.
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}NOTE
모든 이메일 템플릿을 보관할 resources/views/mail 디렉터리를 만들어 두면 좋습니다. 다만 resources/views 디렉터리 안이라면 원하는 위치에 자유롭게 둘 수 있습니다.
일반 텍스트 이메일
이메일의 일반 텍스트(plain-text) 버전을 정의하고 싶다면, 메시지의 Content 정의를 만들 때 일반 텍스트 템플릿을 지정하면 됩니다. view 파라미터와 마찬가지로 text 파라미터에도 이메일 내용을 렌더링할 템플릿 이름을 전달합니다. 메시지의 HTML 버전과 일반 텍스트 버전을 모두 정의할 수도 있습니다.
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
text: 'mail.orders.shipped-text'
);
}의미를 더 분명하게 하고 싶다면 view 파라미터의 별칭(alias)으로 html 파라미터를 사용할 수도 있습니다.
return new Content(
html: 'mail.orders.shipped',
text: 'mail.orders.shipped-text'
);뷰 데이터
public 프로퍼티로 전달하기
일반적으로 이메일의 HTML을 렌더링할 때 사용할 데이터를 뷰에 전달해야 합니다. 뷰에 데이터를 전달하는 방법은 두 가지입니다. 첫 번째로, mailable 클래스에 정의된 모든 public 프로퍼티는 자동으로 뷰에서 사용할 수 있습니다. 따라서 mailable 클래스의 생성자로 데이터를 전달한 뒤, 그 데이터를 클래스의 public 프로퍼티에 설정하면 됩니다.
<?php
namespace App\Mail;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Queue\SerializesModels;
class OrderShipped extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(
public Order $order,
) {}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}
}데이터가 public 프로퍼티에 설정되면 뷰에서 자동으로 사용할 수 있으므로, Blade 템플릿에서 다른 데이터와 똑같이 접근하면 됩니다.
<div>
Price: {{ $order->price }}
</div>`with` 파라미터로 전달하기
템플릿으로 전달하기 전에 이메일 데이터의 형식을 직접 가공하고 싶다면, Content 정의의 with 파라미터를 통해 뷰에 데이터를 수동으로 전달할 수 있습니다. 이 경우에도 보통은 mailable 클래스의 생성자로 데이터를 전달하지만, 템플릿에서 자동으로 사용할 수 없도록 해당 데이터를 protected 또는 private 프로퍼티에 설정해야 합니다.
<?php
namespace App\Mail;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Queue\SerializesModels;
class OrderShipped extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(
protected Order $order,
) {}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
with: [
'orderName' => $this->order->name,
'orderPrice' => $this->order->price,
],
);
}
}with 파라미터로 전달한 데이터는 뷰에서 자동으로 사용할 수 있으므로, Blade 템플릿에서 다른 데이터와 똑같이 접근하면 됩니다.
<div>
Price: {{ $orderPrice }}
</div>첨부 파일
이메일에 파일을 첨부하려면 메시지의 attachments 메서드가 반환하는 배열에 첨부 파일을 추가합니다. 먼저 Attachment 클래스가 제공하는 fromPath 메서드에 파일 경로를 전달해 첨부할 수 있습니다.
use Illuminate\Mail\Mailables\Attachment;
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file'),
];
}메시지에 파일을 첨부할 때 as와 withMime 메서드를 사용해 첨부 파일의 표시 이름과 MIME 타입을 지정할 수도 있습니다.
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}디스크의 파일 첨부하기
파일 시스템 디스크 중 하나에 파일을 저장해 두었다면, fromStorage 첨부 메서드로 이메일에 첨부할 수 있습니다.
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorage('/path/to/file'),
];
}물론 첨부 파일의 이름과 MIME 타입도 지정할 수 있습니다.
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorage('/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}기본 디스크가 아닌 다른 스토리지 디스크를 지정해야 한다면 fromStorageDisk 메서드를 사용합니다.
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorageDisk('s3', '/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}원시 데이터 첨부하기
fromData 첨부 메서드는 원시 바이트 문자열을 첨부 파일로 추가할 때 사용합니다. 예를 들어 메모리에서 PDF를 생성했고, 이를 디스크에 쓰지 않고 이메일에 바로 첨부하고 싶을 때 유용합니다. fromData 메서드는 원시 데이터 바이트를 반환하는 클로저와 첨부 파일에 지정할 이름을 인자로 받습니다.
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromData(fn () => $this->pdf, 'Report.pdf')
->withMime('application/pdf'),
];
}인라인 첨부 파일
이메일에 인라인 이미지를 삽입하는 일은 보통 번거롭지만, Laravel은 이미지를 편리하게 첨부할 수 있는 방법을 제공합니다. 인라인 이미지를 삽입하려면 이메일 템플릿 안에서 $message 변수의 embed 메서드를 사용합니다. Laravel은 모든 이메일 템플릿에서 $message 변수를 자동으로 사용할 수 있게 해 주므로 직접 전달할 필요가 없습니다.
<body>
Here is an image:
<img src="{{ $message->embed($pathToImage) }}">
</body>WARNING
일반 텍스트 메시지는 인라인 첨부 파일을 사용하지 않으므로, 일반 텍스트 메시지 템플릿에서는 $message 변수를 사용할 수 없습니다.
원시 데이터 첨부 파일 삽입하기
이메일 템플릿에 삽입하고 싶은 원시 이미지 데이터 문자열이 이미 있다면, $message 변수의 embedData 메서드를 호출하면 됩니다. embedData 메서드를 호출할 때는 삽입할 이미지에 지정할 파일명을 함께 전달해야 합니다.
<body>
Here is an image from raw data:
<img src="{{ $message->embedData($data, 'example-image.jpg') }}">
</body>Attachable 객체
단순한 문자열 경로로 파일을 첨부하는 것으로 충분한 경우가 많지만, 애플리케이션에서 첨부 대상이 되는 개체가 클래스로 표현되는 경우도 많습니다. 예를 들어 메시지에 사진을 첨부한다면, 그 사진을 나타내는 Photo 모델이 이미 있을 수 있습니다. 그렇다면 Photo 모델을 attach 메서드에 그대로 전달할 수 있으면 편리하지 않을까요? Attachable 객체를 사용하면 바로 그렇게 할 수 있습니다.
시작하려면 메시지에 첨부할 수 있는 객체에 Illuminate\Contracts\Mail\Attachable 인터페이스를 구현합니다. 이 인터페이스는 클래스에 Illuminate\Mail\Attachment 인스턴스를 반환하는 toMailAttachment 메서드를 정의하도록 요구합니다.
<?php
namespace App\Models;
use Illuminate\Contracts\Mail\Attachable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Mail\Attachment;
class Photo extends Model implements Attachable
{
/**
* Get the attachable representation of the model.
*/
public function toMailAttachment(): Attachment
{
return Attachment::fromPath('/path/to/file');
}
}attachable 객체를 정의했다면, 이메일 메시지를 만들 때 attachments 메서드에서 해당 객체의 인스턴스를 반환하면 됩니다.
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [$this->photo];
}물론 첨부 파일 데이터가 Amazon S3 같은 원격 파일 스토리지 서비스에 저장되어 있을 수도 있습니다. Laravel은 애플리케이션의 파일 시스템 디스크에 저장된 데이터로도 첨부 파일 인스턴스를 생성할 수 있습니다.
// 기본 디스크의 파일로 첨부 파일을 생성합니다...
return Attachment::fromStorage($this->path);
// 특정 디스크의 파일로 첨부 파일을 생성합니다...
return Attachment::fromStorageDisk('backblaze', $this->path);또한 메모리에 있는 데이터로 첨부 파일 인스턴스를 만들 수도 있습니다. 이때는 fromData 메서드에 클로저를 전달하며, 클로저는 첨부 파일을 나타내는 원시 데이터를 반환해야 합니다.
return Attachment::fromData(fn () => $this->content, 'Photo Name');Laravel은 첨부 파일을 커스터마이징할 수 있는 추가 메서드도 제공합니다. 예를 들어 as와 withMime 메서드로 파일 이름과 MIME 타입을 지정할 수 있습니다.
return Attachment::fromPath('/path/to/file')
->as('Photo Name')
->withMime('image/jpeg');헤더
발송하는 메시지에 추가 헤더를 붙여야 할 때가 있습니다. 예를 들어 사용자 정의 Message-Id나 그 밖의 임의의 텍스트 헤더를 설정해야 할 수 있습니다.
이를 위해 mailable에 headers 메서드를 정의합니다. headers 메서드는 Illuminate\Mail\Mailables\Headers 인스턴스를 반환해야 합니다. 이 클래스는 messageId, references, text 파라미터를 받으며, 메시지에 필요한 파라미터만 선택해서 전달해도 됩니다.
use Illuminate\Mail\Mailables\Headers;
/**
* Get the message headers.
*/
public function headers(): Headers
{
return new Headers(
messageId: 'custom-message-id@example.com',
references: ['previous-message@example.com'],
text: [
'X-Custom-Header' => 'Custom Value',
],
);
}태그와 메타데이터
Mailgun, Postmark 같은 일부 서드파티 이메일 제공업체는 메시지 "태그"와 "메타데이터"를 지원하며, 이를 이용해 애플리케이션이 보낸 이메일을 그룹화하고 추적할 수 있습니다. Envelope 정의를 통해 이메일 메시지에 태그와 메타데이터를 추가할 수 있습니다.
use Illuminate\Mail\Mailables\Envelope;
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Order Shipped',
tags: ['shipment'],
metadata: [
'order_id' => $this->order->id,
],
);
}애플리케이션에서 Mailgun 드라이버를 사용한다면, Mailgun 문서에서 태그와 메타데이터에 대한 자세한 내용을 확인할 수 있습니다. 마찬가지로 Postmark의 태그와 메타데이터 지원에 대해서는 Postmark 문서를 참고하세요.
애플리케이션에서 Amazon SES로 이메일을 보낸다면, metadata 메서드를 사용해 메시지에 SES "태그"를 첨부해야 합니다.
Symfony 메시지 커스터마이징
Laravel의 메일 기능은 Symfony Mailer를 기반으로 동작합니다. Laravel에서는 메시지를 전송하기 전에 Symfony Message 인스턴스를 인자로 호출되는 사용자 정의 콜백을 등록할 수 있습니다. 이를 통해 메시지를 보내기 전에 세부 사항까지 깊이 있게 커스터마이징할 수 있습니다. 이렇게 하려면 Envelope 정의에 using 파라미터를 정의합니다.
use Illuminate\Mail\Mailables\Envelope;
use Symfony\Component\Mime\Email;
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Order Shipped',
using: [
function (Email $message) {
// ...
},
]
);
}Markdown Mailable
Markdown mailable 메시지를 사용하면 메일 알림의 미리 만들어진 템플릿과 컴포넌트를 mailable에서도 그대로 활용할 수 있습니다. 메시지를 Markdown으로 작성하므로, Laravel이 보기 좋은 반응형 HTML 템플릿을 렌더링하면서 일반 텍스트 버전도 자동으로 생성해 줍니다.
Markdown Mailable 생성하기
Markdown 템플릿이 함께 만들어지는 mailable을 생성하려면 make:mail Artisan 명령어의 --markdown 옵션을 사용합니다.
php artisan make:mail OrderShipped --markdown=mail.orders.shipped그런 다음 mailable의 content 메서드에서 Content 정의를 구성할 때, view 파라미터 대신 markdown 파라미터를 사용합니다.
use Illuminate\Mail\Mailables\Content;
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.orders.shipped',
with: [
'url' => $this->orderUrl,
],
);
}Markdown 메시지 작성하기
Markdown mailable은 Blade 컴포넌트와 Markdown 문법을 함께 사용합니다. 덕분에 Laravel이 미리 만들어 둔 이메일 UI 컴포넌트를 활용하면서 메일 메시지를 쉽게 구성할 수 있습니다.
<x-mail::message>
# Order Shipped
Your order has been shipped!
<x-mail::button :url="$url">
View Order
</x-mail::button>
Thanks,<br>
{{ config('app.name') }}
</x-mail::message>NOTE
Markdown 이메일을 작성할 때는 과도하게 들여쓰기하지 마세요. Markdown 표준에 따라 Markdown 파서는 들여쓰기된 내용을 코드 블록으로 렌더링합니다.
버튼 컴포넌트
버튼 컴포넌트는 가운데 정렬된 버튼 링크를 렌더링합니다. 이 컴포넌트는 url과 선택 사항인 color, 두 개의 인자를 받습니다. 지원하는 색상은 primary, success, error입니다. 한 메시지에 버튼 컴포넌트를 원하는 만큼 추가할 수 있습니다.
<x-mail::button :url="$url" color="success">
View Order
</x-mail::button>패널 컴포넌트
패널 컴포넌트는 주어진 텍스트 블록을 메시지의 나머지 부분과 배경색이 약간 다른 패널 안에 렌더링합니다. 이를 이용해 특정 텍스트 블록에 주의를 끌 수 있습니다.
<x-mail::panel>
This is the panel content.
</x-mail::panel>테이블 컴포넌트
테이블 컴포넌트는 Markdown 테이블을 HTML 테이블로 변환합니다. 이 컴포넌트는 Markdown 테이블을 콘텐츠로 받습니다. 기본 Markdown 테이블 정렬 문법을 사용해 열 정렬도 지원합니다.
<x-mail::table>
| Laravel | Table | Example |
| ------------- | :-----------: | ------------: |
| Col 2 is | Centered | $10 |
| Col 3 is | Right-Aligned | $20 |
</x-mail::table>컴포넌트 커스터마이징
Markdown 메일 컴포넌트를 모두 애플리케이션으로 내보내 직접 커스터마이징할 수 있습니다. 컴포넌트를 내보내려면 vendor:publish Artisan 명령어로 laravel-mail 에셋 태그를 퍼블리시합니다.
php 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 파사드의 to 메서드를 사용합니다. to 메서드는 이메일 주소, 사용자 인스턴스, 또는 사용자 컬렉션을 받을 수 있습니다. 객체나 객체 컬렉션을 전달하면 메일러가 수신자를 결정할 때 해당 객체의 email과 name 프로퍼티를 자동으로 사용하므로, 객체에 이 속성들이 있는지 확인해야 합니다. 수신자를 지정했다면 mailable 클래스의 인스턴스를 send 메서드에 전달합니다.
<?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
{
/**
* Ship the given order.
*/
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);
// 주문을 배송 처리합니다...
Mail::to($request->user())->send(new OrderShipped($order));
return redirect('/orders');
}
}메시지를 보낼 때 "to" 수신자만 지정할 수 있는 것은 아닙니다. 각 메서드를 체이닝하여 "to", "cc", "bcc" 수신자를 자유롭게 지정할 수 있습니다.
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));수신자 목록 순회하기
수신자 배열이나 이메일 주소 배열을 순회하면서 mailable을 보내야 할 때가 있습니다. 그런데 to 메서드는 mailable의 수신자 목록에 이메일 주소를 계속 추가(append)하므로, 반복문을 돌 때마다 이전 수신자 모두에게 이메일이 다시 전송됩니다. 따라서 수신자마다 항상 mailable 인스턴스를 새로 생성해야 합니다.
foreach (['taylor@example.com', 'dries@example.com'] as $recipient) {
Mail::to($recipient)->send(new OrderShipped($order));
}특정 메일러로 메일 보내기
기본적으로 Laravel은 애플리케이션의 mail 설정 파일에서 default로 지정된 메일러를 사용해 이메일을 보냅니다. 하지만 mailer 메서드를 사용하면 특정 메일러 설정으로 메시지를 보낼 수 있습니다.
Mail::mailer('postmark')
->to($request->user())
->send(new OrderShipped($order));메일 큐 처리하기
메일 메시지를 큐에 넣기
이메일 전송은 애플리케이션의 응답 시간에 부정적인 영향을 줄 수 있기 때문에, 많은 개발자가 이메일 메시지를 큐에 넣어 백그라운드에서 전송합니다. Laravel은 내장된 통합 큐 API를 통해 이를 쉽게 처리할 수 있습니다. 메일 메시지를 큐에 넣으려면 수신자를 지정한 뒤 Mail 파사드의 queue 메서드를 사용합니다.
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->queue(new OrderShipped($order));이 메서드는 메시지가 백그라운드에서 전송되도록 큐에 Job을 푸시하는 일을 자동으로 처리합니다. 이 기능을 사용하기 전에 큐를 설정해야 합니다.
메시지 전송 지연시키기
큐에 넣은 이메일 메시지의 전송을 지연시키고 싶다면 later 메서드를 사용합니다. later 메서드는 첫 번째 인자로 메시지를 언제 보낼지 나타내는 DateTime 인스턴스를 받습니다.
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->later(now()->plus(minutes: 10), new OrderShipped($order));특정 큐에 푸시하기
make:mail 명령어로 생성한 모든 mailable 클래스는 Illuminate\Bus\Queueable 트레이트를 사용하므로, 어떤 mailable 클래스 인스턴스에서든 onQueue와 onConnection 메서드를 호출해 메시지의 커넥션과 큐 이름을 지정할 수 있습니다.
$message = (new OrderShipped($order))
->onConnection('sqs')
->onQueue('emails');
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->queue($message);또는 mailable 클래스에 Connection과 Queue 속성(attribute)을 사용해 커넥션과 큐를 지정할 수도 있습니다.
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;
#[Connection('sqs')]
#[Queue('emails')]
class OrderShipped extends Mailable
{
// ...
}기본으로 큐 처리하기
항상 큐로 처리하고 싶은 mailable 클래스가 있다면, 해당 클래스에 ShouldQueue 계약(contract)을 구현하면 됩니다. 이제 메일을 보낼 때 send 메서드를 호출하더라도, mailable이 이 계약을 구현하고 있으므로 여전히 큐에 들어갑니다.
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShipped extends Mailable implements ShouldQueue
{
// ...
}큐 처리되는 Mailable과 데이터베이스 트랜잭션
큐에 넣는 mailable을 데이터베이스 트랜잭션 안에서 디스패치하면, 트랜잭션이 커밋되기 전에 큐가 해당 Job을 처리할 수 있습니다. 이 경우 트랜잭션 중에 모델이나 데이터베이스 레코드에 수행한 변경 사항이 아직 데이터베이스에 반영되지 않았을 수 있고, 트랜잭션 안에서 생성한 모델이나 레코드가 데이터베이스에 존재하지 않을 수도 있습니다. mailable이 이러한 모델에 의존한다면, 큐에 넣은 mailable을 전송하는 Job을 처리할 때 예기치 않은 오류가 발생할 수 있습니다.
큐 커넥션의 after_commit 설정 옵션이 false로 되어 있더라도, 메일 메시지를 보낼 때 afterCommit 메서드를 호출하면 열려 있는 모든 데이터베이스 트랜잭션이 커밋된 후에 특정 큐 mailable을 디스패치하도록 지정할 수 있습니다.
Mail::to($request->user())->send(
(new OrderShipped($order))->afterCommit()
);또는 mailable의 생성자에서 afterCommit 메서드를 호출할 수도 있습니다.
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class OrderShipped extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct()
{
$this->afterCommit();
}
}NOTE
이러한 문제를 해결하는 방법을 더 자세히 알아보려면 큐 Job과 데이터베이스 트랜잭션 문서를 참고하세요.
큐 이메일 전송 실패 처리
큐에 넣은 이메일이 실패하면, 큐 mailable 클래스에 failed 메서드가 정의되어 있는 경우 이 메서드가 호출됩니다. 큐 이메일이 실패한 원인인 Throwable 인스턴스가 failed 메서드로 전달됩니다.
<?php
namespace App\Mail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Throwable;
class OrderDelayed extends Mailable implements ShouldQueue
{
use SerializesModels;
/**
* Handle a queued email's failure.
*/
public function failed(Throwable $exception): void
{
// ...
}
}Mailable 렌더링하기
mailable을 전송하지 않고 HTML 내용만 가져오고 싶을 때가 있습니다. 이럴 때는 mailable의 render 메서드를 호출합니다. 이 메서드는 평가(evaluate)된 mailable의 HTML 내용을 문자열로 반환합니다.
use App\Mail\InvoicePaid;
use App\Models\Invoice;
$invoice = Invoice::find(1);
return (new InvoicePaid($invoice))->render();브라우저에서 Mailable 미리보기
mailable의 템플릿을 디자인할 때는 일반적인 Blade 템플릿처럼 렌더링된 mailable을 브라우저에서 빠르게 미리 확인할 수 있으면 편리합니다. 그래서 Laravel은 라우트 클로저나 컨트롤러에서 mailable을 직접 반환할 수 있게 해 줍니다. mailable을 반환하면 렌더링되어 브라우저에 표시되므로, 실제 이메일 주소로 보내지 않고도 디자인을 빠르게 확인할 수 있습니다.
Route::get('/mailable', function () {
$invoice = App\Models\Invoice::find(1);
return new App\Mail\InvoicePaid($invoice);
});Mailable 현지화하기
Laravel에서는 요청의 현재 로케일과 다른 로케일로 mailable을 보낼 수 있으며, 메일이 큐에 들어가더라도 이 로케일을 기억합니다.
이를 위해 Mail 파사드는 원하는 언어를 설정하는 locale 메서드를 제공합니다. mailable의 템플릿을 평가하는 동안 애플리케이션이 해당 로케일로 전환되고, 평가가 끝나면 이전 로케일로 되돌아갑니다.
Mail::to($request->user())->locale('es')->send(
new OrderShipped($order)
);사용자 선호 로케일
애플리케이션에서 사용자별 선호 로케일을 저장해 두는 경우가 있습니다. 하나 이상의 모델에 HasLocalePreference 계약(contract)을 구현하면, 메일을 보낼 때 저장된 로케일을 사용하도록 Laravel에 지시할 수 있습니다.
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Model implements HasLocalePreference
{
/**
* Get the user's preferred locale.
*/
public function preferredLocale(): string
{
return $this->locale;
}
}이 인터페이스를 구현하면 Laravel은 해당 모델에 mailable과 알림을 보낼 때 선호 로케일을 자동으로 사용합니다. 따라서 이 인터페이스를 사용할 때는 locale 메서드를 호출할 필요가 없습니다.
Mail::to($request->user())->send(new OrderShipped($order));테스트
Mailable 내용 테스트
Laravel은 mailable의 구조를 검사하는 다양한 메서드를 제공합니다. 또한 mailable이 기대한 내용을 담고 있는지 테스트할 수 있는 편리한 메서드도 여러 가지 제공합니다.
Pest
use App\Mail\InvoicePaid;
use App\Models\User;
test('mailable content', function () {
$user = User::factory()->create();
$mailable = new InvoicePaid($user);
$mailable->assertFrom('jeffrey@example.com');
$mailable->assertTo('taylor@example.com');
$mailable->assertHasCc('abigail@example.com');
$mailable->assertHasBcc('victoria@example.com');
$mailable->assertHasReplyTo('tyler@example.com');
$mailable->assertHasSubject('Invoice Paid');
$mailable->assertHasTag('example-tag');
$mailable->assertHasMetadata('key', 'value');
$mailable->assertSeeInHtml($user->email);
$mailable->assertDontSeeInHtml('Invoice Not Paid');
$mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);
$mailable->assertSeeInText($user->email);
$mailable->assertDontSeeInText('Invoice Not Paid');
$mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);
$mailable->assertHasAttachment('/path/to/file');
$mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));
$mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);
$mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
$mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
});PHPUnit
use App\Mail\InvoicePaid;
use App\Models\User;
public function test_mailable_content(): void
{
$user = User::factory()->create();
$mailable = new InvoicePaid($user);
$mailable->assertFrom('jeffrey@example.com');
$mailable->assertTo('taylor@example.com');
$mailable->assertHasCc('abigail@example.com');
$mailable->assertHasBcc('victoria@example.com');
$mailable->assertHasReplyTo('tyler@example.com');
$mailable->assertHasSubject('Invoice Paid');
$mailable->assertHasTag('example-tag');
$mailable->assertHasMetadata('key', 'value');
$mailable->assertSeeInHtml($user->email);
$mailable->assertDontSeeInHtml('Invoice Not Paid');
$mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);
$mailable->assertSeeInText($user->email);
$mailable->assertDontSeeInText('Invoice Not Paid');
$mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);
$mailable->assertHasAttachment('/path/to/file');
$mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));
$mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);
$mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
$mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
}예상하셨겠지만, "HTML" 어설션은 mailable의 HTML 버전에 특정 문자열이 포함되어 있는지 검증하고, "text" 어설션은 mailable의 일반 텍스트 버전에 특정 문자열이 포함되어 있는지 검증합니다.
Mailable 전송 테스트
mailable의 내용 테스트는 특정 사용자에게 mailable이 "전송"되었는지 검증하는 테스트와 분리해서 작성하기를 권장합니다. 일반적으로 mailable의 내용은 테스트 대상 코드와 관련이 없으며, Laravel이 해당 mailable을 전송하도록 지시받았는지만 검증해도 충분합니다.
Mail 파사드의 fake 메서드를 사용하면 메일이 실제로 전송되지 않도록 할 수 있습니다. Mail 파사드의 fake 메서드를 호출한 뒤에는 mailable이 사용자에게 전송되도록 지시되었는지 검증할 수 있고, mailable이 받은 데이터까지 확인할 수 있습니다.
Pest
<?php
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
test('orders can be shipped', function () {
Mail::fake();
// 주문 배송 처리를 수행합니다...
// 전송된 mailable이 없는지 검증합니다...
Mail::assertNothingSent();
// mailable이 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class);
// mailable이 두 번 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class, 2);
// mailable이 특정 이메일 주소로 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// mailable이 여러 이메일 주소로 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// mailable이 전송되지 않았는지 검증합니다...
Mail::assertNotSent(AnotherMailable::class);
// mailable이 두 번 전송되었는지 검증합니다...
Mail::assertSentTimes(OrderShipped::class, 2);
// mailable이 정확히 한 번 전송되었는지 검증합니다...
Mail::assertSentOnce(OrderShipped::class);
// 총 3개의 mailable이 전송되었는지 검증합니다...
Mail::assertSentCount(3);
});PHPUnit
<?php
namespace Tests\Feature;
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_orders_can_be_shipped(): void
{
Mail::fake();
// 주문 배송 처리를 수행합니다...
// 전송된 mailable이 없는지 검증합니다...
Mail::assertNothingSent();
// mailable이 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class);
// mailable이 두 번 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class, 2);
// mailable이 특정 이메일 주소로 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// mailable이 여러 이메일 주소로 전송되었는지 검증합니다...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// mailable이 전송되지 않았는지 검증합니다...
Mail::assertNotSent(AnotherMailable::class);
// mailable이 두 번 전송되었는지 검증합니다...
Mail::assertSentTimes(OrderShipped::class, 2);
// mailable이 정확히 한 번 전송되었는지 검증합니다...
Mail::assertSentOnce(OrderShipped::class);
// 총 3개의 mailable이 전송되었는지 검증합니다...
Mail::assertSentCount(3);
}
}mailable을 백그라운드에서 전송하도록 큐에 넣는 경우에는 assertSent 대신 assertQueued 메서드를 사용해야 합니다.
Mail::assertQueued(OrderShipped::class);
Mail::assertQueuedOnce(OrderShipped::class);
Mail::assertNotQueued(OrderShipped::class);
Mail::assertNothingQueued();
Mail::assertQueuedCount(3);assertOutgoingCount 메서드를 사용하면 전송되었거나 큐에 들어간 mailable의 총 개수를 검증할 수도 있습니다.
Mail::assertOutgoingCount(3);assertSent, assertNotSent, assertQueued, assertNotQueued 메서드에 클로저를 전달하면, 주어진 "진위 테스트(truth test)"를 통과하는 mailable이 전송되었는지 검증할 수 있습니다. 진위 테스트를 통과하는 mailable이 하나라도 전송되었다면 어설션은 성공합니다.
Mail::assertSent(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});Mail 파사드의 어설션 메서드를 호출할 때, 전달한 클로저가 받는 mailable 인스턴스에는 mailable을 검사하는 데 유용한 메서드들이 있습니다.
Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) {
return $mail->hasTo($user->email) &&
$mail->hasCc('...') &&
$mail->hasBcc('...') &&
$mail->hasReplyTo('...') &&
$mail->hasFrom('...') &&
$mail->hasSubject('...') &&
$mail->hasMetadata('order_id', $mail->order->id) &&
$mail->usesMailer('ses');
});mailable 인스턴스에는 첨부 파일을 검사하는 데 유용한 메서드도 여러 가지 있습니다.
use Illuminate\Mail\Mailables\Attachment;
Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {
return $mail->hasAttachment(
Attachment::fromPath('/path/to/file')
->as('name.pdf')
->withMime('application/pdf')
);
});
Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {
return $mail->hasAttachment(
Attachment::fromStorageDisk('s3', '/path/to/file')
);
});
Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) {
return $mail->hasAttachment(
Attachment::fromData(fn () => $pdfData, 'name.pdf')
);
});메일이 전송되지 않았음을 검증하는 메서드가 assertNotSent와 assertNotQueued, 두 가지라는 점을 눈치채셨을 것입니다. 메일이 전송되지도 않았고 큐에 들어가지도 않았음을 검증하고 싶을 때가 있습니다. 이럴 때는 assertNothingOutgoing과 assertNotOutgoing 메서드를 사용하면 됩니다.
Mail::assertNothingOutgoing();
Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});메일과 로컬 개발 환경
이메일을 전송하는 애플리케이션을 개발할 때는 실제 이메일 주소로 메일이 발송되는 것을 원하지 않을 것입니다. Laravel은 로컬 개발 중에 이메일의 실제 전송을 "비활성화"하는 여러 방법을 제공합니다.
로그 드라이버
log 메일 드라이버는 이메일을 전송하는 대신 모든 이메일 메시지를 로그 파일에 기록하여 확인할 수 있게 해 줍니다. 이 드라이버는 보통 로컬 개발 중에만 사용합니다. 환경별로 애플리케이션을 설정하는 방법에 대한 자세한 내용은 설정 문서를 확인하세요.
HELO / Mailtrap Email Sandbox / Mailpit
또는 HELO나 Mailtrap Email Sandbox 같은 서비스와 smtp 드라이버를 사용해, 이메일 메시지를 "더미" 메일함으로 보내 실제 이메일 클라이언트에서 확인할 수도 있습니다. 이 방식은 Mailtrap Email Sandbox의 메시지 뷰어에서 최종 이메일을 실제로 확인할 수 있다는 장점이 있습니다.
Laravel Sail을 사용 중이라면 Mailpit으로 메시지를 미리 볼 수 있습니다. Sail이 실행 중일 때는 http://localhost:8025에서 Mailpit 인터페이스에 접속할 수 있습니다.
전역 `to` 주소 사용하기
마지막으로, Mail 파사드가 제공하는 alwaysTo 메서드를 호출해 전역 "to" 주소를 지정할 수 있습니다. 이 메서드는 보통 애플리케이션 서비스 프로바이더 중 하나의 boot 메서드에서 호출합니다.
use Illuminate\Support\Facades\Mail;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
if ($this->app->environment('local')) {
Mail::alwaysTo('taylor@example.com');
}
}alwaysTo 메서드를 사용하면 메일 메시지의 추가 "cc" 또는 "bcc" 주소는 모두 제거됩니다.
이벤트
Laravel은 메일 메시지를 전송하는 동안 두 가지 이벤트를 디스패치합니다. MessageSending 이벤트는 메시지가 전송되기 전에, MessageSent 이벤트는 메시지가 전송된 후에 디스패치됩니다. 이 이벤트들은 메일이 큐에 들어갈 때가 아니라 전송될 때 디스패치된다는 점에 유의하세요. 애플리케이션에서 이 이벤트에 대한 이벤트 리스너를 만들 수 있습니다.
use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;
class LogMessage
{
/**
* Handle the event.
*/
public function handle(MessageSending $event): void
{
// ...
}
}커스텀 트랜스포트
Laravel은 다양한 메일 트랜스포트를 포함하고 있지만, Laravel이 기본으로 지원하지 않는 다른 서비스를 통해 이메일을 전달하려면 직접 트랜스포트를 작성해야 할 수 있습니다. 먼저 Symfony\Component\Mailer\Transport\AbstractTransport 클래스를 상속하는 클래스를 정의합니다. 그런 다음 트랜스포트에 doSend와 __toString 메서드를 구현합니다.
<?php
namespace App\Mail;
use MailchimpTransactional\ApiClient;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\MessageConverter;
class MailchimpTransport extends AbstractTransport
{
/**
* Create a new Mailchimp transport instance.
*/
public function __construct(
protected ApiClient $client,
) {
parent::__construct();
}
/**
* {@inheritDoc}
*/
protected function doSend(SentMessage $message): void
{
$email = MessageConverter::toEmail($message->getOriginalMessage());
$this->client->messages->send(['message' => [
'from_email' => $email->getFrom(),
'to' => collect($email->getTo())->map(function (Address $email) {
return ['email' => $email->getAddress(), 'type' => 'to'];
})->all(),
'subject' => $email->getSubject(),
'text' => $email->getTextBody(),
]]);
}
/**
* Get the string representation of the transport.
*/
public function __toString(): string
{
return 'mailchimp';
}
}커스텀 트랜스포트를 정의했다면 Mail 파사드가 제공하는 extend 메서드로 등록할 수 있습니다. 보통 애플리케이션의 AppServiceProvider에 있는 boot 메서드 안에서 등록합니다. extend 메서드에 전달한 클로저에는 $config 인자가 전달됩니다. 이 인자에는 애플리케이션의 config/mail.php 설정 파일에서 해당 메일러에 정의된 설정 배열이 담겨 있습니다.
use App\Mail\MailchimpTransport;
use Illuminate\Support\Facades\Mail;
use MailchimpTransactional\ApiClient;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Mail::extend('mailchimp', function (array $config = []) {
$client = new ApiClient;
$client->setApiKey($config['key']);
return new MailchimpTransport($client);
});
}커스텀 트랜스포트를 정의하고 등록했다면, 애플리케이션의 config/mail.php 설정 파일에 새 트랜스포트를 사용하는 메일러 정의를 만들 수 있습니다.
'mailchimp' => [
'transport' => 'mailchimp',
'key' => env('MAILCHIMP_API_KEY'),
// ...
],추가 Symfony 트랜스포트
Laravel은 Mailgun, Postmark처럼 Symfony가 유지 관리하는 기존 메일 트랜스포트 일부를 기본으로 지원합니다. 하지만 Symfony가 유지 관리하는 다른 트랜스포트를 Laravel에 추가로 지원하고 싶을 수도 있습니다. 이 경우 Composer로 필요한 Symfony 메일러를 설치한 뒤 Laravel에 트랜스포트를 등록하면 됩니다. 예를 들어 "Brevo"(구 "Sendinblue") Symfony 메일러를 설치하고 등록할 수 있습니다.
composer require symfony/brevo-mailer symfony/http-clientBrevo 메일러 패키지를 설치했다면, 애플리케이션의 services 설정 파일에 Brevo API 자격 증명 항목을 추가합니다.
'brevo' => [
'key' => env('BREVO_API_KEY'),
],다음으로 Mail 파사드의 extend 메서드를 사용해 Laravel에 트랜스포트를 등록합니다. 보통 서비스 프로바이더의 boot 메서드 안에서 등록합니다.
use Illuminate\Support\Facades\Mail;
use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory;
use Symfony\Component\Mailer\Transport\Dsn;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Mail::extend('brevo', function () {
return (new BrevoTransportFactory)->create(
new Dsn(
'brevo+api',
'default',
config('services.brevo.key')
)
);
});
}트랜스포트를 등록했다면, 애플리케이션의 config/mail.php 설정 파일에 새 트랜스포트를 사용하는 메일러 정의를 만들 수 있습니다.
'brevo' => [
'transport' => 'brevo',
// ...
],