메일
업데이트됨번역일: 2026년 9월 30일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 9월 30일
- 번역 갱신
- 2026년 9월 30일
메일
소개
메일 전송은 복잡할 필요가 없습니다. Laravel은 인기 있는 Symfony Mailer 컴포넌트를 기반으로 깔끔하고 간결한 이메일 API를 제공합니다. Laravel과 Symfony Mailer는 SMTP, Mailgun, Postmark, Resend, Amazon SES, sendmail을 통한 메일 전송을 위한 드라이버를 제공하므로, 로컬 또는 클라우드 기반 서비스 중에서 원하는 드라이버로 빠르게 메일 전송을 시작할 수 있습니다.
설정
Laravel의 메일 서비스는 애플리케이션의 config/mail.php 설정 파일을 통해 구성할 수 있습니다. 이 파일 내에 설정된 각 메일러(mailer)는 고유한 설정과 자체적인 "트랜스포트(transport)"를 가질 수 있으며, 이를 통해 애플리케이션이 특정 이메일 메시지를 전송할 때 서로 다른 이메일 서비스를 사용할 수 있습니다. 예를 들어, 애플리케이션은 Postmark로 트랜잭션 이메일을 전송하고, Amazon SES로는 대량 이메일을 전송할 수 있습니다.
mail 설정 파일 내에는 mailers 설정 배열이 있습니다. 이 배열에는 Laravel이 지원하는 주요 메일 드라이버(트랜스포트)별로 샘플 설정 항목이 담겨 있으며, default 설정 값은 애플리케이션이 이메일 메시지를 전송할 때 기본적으로 사용할 메일러를 지정합니다.
드라이버 / 트랜스포트 사전 준비 사항
Mailgun, Postmark, Resend, MailerSend와 같은 API 기반 드라이버는 SMTP 서버를 통해 메일을 전송하는 것보다 대체로 더 간단하고 빠릅니다. 가능하다면 이러한 드라이버 중 하나를 사용하는 것을 권장합니다.
Mailgun 드라이버
Mailgun 드라이버를 사용하려면 Composer를 통해 Symfony의 Mailgun Mailer 트랜스포트를 설치하세요.
composer require symfony/mailgun-mailer symfony/http-client다음으로, 애플리케이션의 config/mail.php 설정 파일에서 다음 두 가지를 설정합니다. 첫째, default 옵션을 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'),
],Postmark 드라이버
Postmark 드라이버를 사용하려면 Composer를 통해 Symfony의 Postmark Mailer 트랜스포트를 설치하세요.
composer require symfony/postmark-mailer symfony/http-client다음으로, 애플리케이션의 config/mail.php 설정 파일에서 default 옵션을 postmark로 설정합니다. 기본 메일러를 설정한 후, config/services.php 설정 파일에 다음 옵션이 포함되어 있는지 확인합니다.
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],특정 메일러가 사용해야 할 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_KEY'),
],SES 드라이버
Amazon SES(Simple Email Service) 드라이버를 사용하려면 먼저 AWS PHP SDK를 설치해야 합니다. 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',
],
);
}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'],
],
],
],MailerSend 드라이버
MailerSend는 트랜잭션 이메일 및 SMS 서비스를 제공하는 업체로, mailersend/laravel-driver 패키지를 통해 자체 API 기반 메일 드라이버를 Laravel용으로 유지 관리하고 있습니다. 드라이버 설정에 대한 문서는 해당 패키지의 저장소에서 확인할 수 있습니다.
장애 조치(Failover) 설정
애플리케이션에서 사용할 "기본" 메일 전송 드라이버가 다운되었을 때, 다운된 상태에서도 계속 메일을 전송할 수 있도록 하나 이상의 백업 메일 전송 설정을 정의해두는 것이 유용할 수 있습니다.
이를 위해서는 failover 트랜스포트를 사용하는 메일러를 애플리케이션의 mail 설정 파일에서 정의해야 합니다. 애플리케이션의 failover 메일러 설정 배열에는 실제 메일 전송에 사용할 메일러들을 선택 순서대로 나열한 mailers 배열이 포함되어야 합니다.
'mailers' => [
'failover' => [
'transport' => 'failover',
'mailers' => [
'postmark',
'mailgun',
'sendmail',
],
// 'retry_after' => 60,
],
// ...
],장애 조치 메일러를 정의했다면, 애플리케이션의 mail 설정 파일에서 default 설정 키의 값을 이 메일러의 이름으로 설정하여 애플리케이션이 사용할 기본 메일러로 지정해야 합니다.
'default' => env('MAIL_MAILER', 'failover'),라운드 로빈(Round Robin) 설정
round-robin 트랜스포트는 여러 메일러에 걸쳐 메일 전송 작업을 분산시킬 수 있게 해줍니다. 시작하려면 애플리케이션의 mail 설정 파일 내에 round-robin 트랜스포트를 사용하는 메일러를 정의합니다. 이 메일러의 설정 배열에는 실제로 메일 전송에 사용될 메일러들의 목록을 담은 mailers 배열이 포함되어야 합니다.
'mailers' => [
'round-robin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
],
// ...
],라운드 로빈 메일러를 정의했다면, 애플리케이션의 mail 설정 파일에서 default 설정 키의 값을 이 메일러의 이름으로 설정하여 애플리케이션이 사용할 기본 메일러로 지정해야 합니다.
'default' => env('MAIL_MAILER', 'round-robin'),라운드 로빈 트랜스포트는 설정된 메일러 목록에서 무작위로 하나의 메일러를 선택한 뒤, 이후 전송되는 메일마다 다음 사용 가능한 메일러로 순서대로 전환합니다. 이는 장애 조치(failover) 트랜스포트가 *최대 가용성(maximum availability)*을 달성하는 데 도움을 주는 것과 달리, 라운드 로빈 트랜스포트는 *부하 분산(load balancing)*을 통해 이러한 이점을 제공한다는 차이가 있습니다.
NOTE
이 두 가지 개념을 좀 더 구체적으로 비교하면, 장애 조치 메일러는 마치 "1순위 우체국이 문을 닫으면 2순위 우체국으로 간다"는 개념이라면, 라운드 로빈 메일러는 "매번 번갈아 가면서 우체국을 이용해 업무 부하를 나눈다"는 개념에 가깝습니다.
메일
시작하기
이메일 발송은 생각보다 훨씬 간단합니다. 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 서버를 통한 발송보다 더 간단하고 빠릅니다. 가능하다면 이런 API 기반 드라이버 중 하나를 사용하는 것을 권장합니다.
Cloudflare 드라이버
Cloudflare 드라이버를 사용하려면 Composer로 Symfony HTTP 클라이언트를 설치하세요.
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',
],미국(United States) 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 메시지 스트림을 지정하고 싶다면, 애플리케이션의 config/mail.php 설정 파일에 있는 해당 메일러 설정 배열에 message_stream_id 옵션을 추가하면 됩니다.
'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 드라이버를 사용하려면 먼저 PHP용 Amazon AWS SDK를 설치해야 합니다. 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 임시 자격 증명(temporary credentials)을 사용하려면, 애플리케이션의 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의 구독 관리 기능(subscription management features)을 사용하려면, 메일 메시지의 headers 메서드가 반환하는 배열에 X-Ses-List-Management-Options 헤더를 포함시키면 됩니다.
/**
* 메시지 헤더를 반환합니다.
*/
public function headers(): Headers
{
return new Headers(
text: [
'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic',
],
);
}SES 테넌트(tenant)를 통해 이메일을 보내려면, 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라운드 로빈(Round Robin) 설정
roundrobin 전송 방식을 사용하면 메일 발송 작업을 여러 메일러에 분산시킬 수 있습니다. 시작하려면 애플리케이션의 mail 설정 파일에서 roundrobin 전송 방식을 사용하는 메일러를 정의하세요. roundrobin 메일러의 설정 배열에는 발송에 사용할 메일러들을 담은 mailers 배열을 포함해야 합니다.
'mailers' => [
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
// ...
],라운드 로빈 메일러를 정의했다면, 애플리케이션의 mail 설정 파일에서 default 설정 키의 값으로 해당 메일러 이름을 지정해 기본 메일러로 사용하도록 설정하세요.
'default' => env('MAIL_MAILER', 'roundrobin'),라운드 로빈 전송 방식은 설정된 메일러 목록 중 하나를 무작위로 선택한 후, 이어지는 각 이메일마다 순서대로 다음 메일러로 전환하며 발송합니다. failover 전송 방식이 고가용성(high availability) 확보를 목적으로 한다면, roundrobin 전송 방식은 *부하 분산(load balancing)*을 제공하는 데 목적이 있습니다.
NOTE
두 방식은 목적이 다릅니다. 장애 대비가 필요하다면 failover를, 여러 메일 발송 서비스에 트래픽을 고르게 분산하고 싶다면 roundrobin을 사용하세요.
메일
Mailable 클래스 생성하기
라라벨 애플리케이션에서 발송하는 이메일의 각 종류는 "Mailable" 클래스로 표현됩니다. 이 클래스들은 app/Mail 디렉터리에 저장됩니다. 처음 프로젝트를 열었을 때 이 디렉터리가 보이지 않더라도 걱정할 필요는 없습니다. make:mail Artisan 명령어로 첫 번째 Mailable 클래스를 생성하면 자동으로 만들어집니다:
php artisan make:mail OrderShippedNOTE
Mailable 클래스는 이메일 한 통을 "설계도"처럼 표현하는 개념입니다. "주문 배송 완료 안내"처럼 발송하는 이메일의 종류마다 별도의 Mailable 클래스를 만드는 것이 일반적인 패턴입니다. 예를 들어 OrderShipped, WelcomeEmail, PasswordReset처럼 목적에 맞게 이름을 지어 관리하면 코드 가독성이 높아집니다.
메일
Mailable 작성하기
Mailable 클래스를 생성했다면, 이제 파일을 열어 내부 구조를 살펴보겠습니다. Mailable 클래스의 설정은 주로 envelope, content, attachments 세 메서드를 통해 이루어집니다.
envelope메서드는Illuminate\Mail\Mailables\Envelope객체를 반환하며, 메일 제목과 (경우에 따라) 수신자를 정의합니다.content메서드는Illuminate\Mail\Mailables\Content객체를 반환하며, 메일 내용을 렌더링할 때 사용할 Blade 템플릿을 정의합니다.
발신자 설정하기
Envelope를 사용하는 방법
먼저 메일의 발신자, 즉 "누가 보내는 메일인지"를 설정하는 방법부터 살펴보겠습니다. 발신자를 지정하는 방법은 두 가지입니다. 첫 번째는 메시지의 envelope에 "from" 주소를 직접 지정하는 방법입니다.
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
/**
* 메시지 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' => [
'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 템플릿 엔진이 제공하는 모든 기능을 그대로 활용할 수 있습니다.
/**
* 메시지 콘텐츠 정의를 가져옵니다.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}NOTE
메일 템플릿을 모아둘 resources/views/mail 디렉터리를 만들어 사용하는 것을 추천합니다. 다만 resources/views 안이라면 원하는 위치 어디에든 자유롭게 템플릿을 둘 수 있습니다.
일반 텍스트 메일
메일의 일반 텍스트(plain-text) 버전을 함께 제공하고 싶다면, Content 정의에 text 파라미터로 텍스트 템플릿을 지정하면 됩니다. view 파라미터와 마찬가지로 text 파라미터에도 렌더링에 사용할 템플릿 이름을 지정합니다. HTML 버전과 텍스트 버전을 동시에 정의하는 것도 가능합니다.
/**
* 메시지 콘텐츠 정의를 가져옵니다.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
text: 'mail.orders.shipped-text'
);
}가독성을 위해 view 파라미터의 별칭으로 html 파라미터를 사용할 수도 있습니다.
return new Content(
html: 'mail.orders.shipped',
text: 'mail.orders.shipped-text'
);뷰에 데이터 전달하기
public 프로퍼티를 이용하는 방법
메일의 HTML을 렌더링할 때 뷰에 데이터를 전달해야 하는 경우가 많습니다. 뷰에 데이터를 전달하는 방법은 두 가지입니다. 첫 번째는 Mailable 클래스에 정의된 public 프로퍼티를 이용하는 방법으로, 이렇게 정의된 프로퍼티는 자동으로 뷰에서 사용할 수 있게 됩니다. 예를 들어 생성자로 데이터를 전달받아 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;
/**
* 새 메시지 인스턴스를 생성합니다.
*/
public function __construct(
public Order $order,
) {}
/**
* 메시지 콘텐츠 정의를 가져옵니다.
*/
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;
/**
* 새 메시지 인스턴스를 생성합니다.
*/
public function __construct(
protected Order $order,
) {}
/**
* 메시지 콘텐츠 정의를 가져옵니다.
*/
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;
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file'),
];
}파일을 첨부할 때 as, withMime 메서드를 사용해 표시 이름이나 MIME 타입을 지정할 수도 있습니다.
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}디스크에 저장된 파일 첨부하기
파일시스템 디스크에 저장해둔 파일이 있다면 fromStorage 메서드로 메일에 첨부할 수 있습니다.
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorage('/path/to/file'),
];
}물론 첨부파일의 이름과 MIME 타입도 함께 지정할 수 있습니다.
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorage('/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}기본 디스크가 아닌 다른 디스크를 지정해야 한다면 fromStorageDisk 메서드를 사용하면 됩니다.
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorageDisk('s3', '/path/to/file')
->as('name.pdf')
->withMime('application/pdf'),
];
}원시(raw) 데이터 첨부하기
fromData 메서드를 사용하면 바이트 문자열 형태의 원시 데이터를 그대로 첨부파일로 만들 수 있습니다. 예를 들어 메모리 상에서 PDF를 생성한 뒤, 이를 디스크에 저장하지 않고 바로 메일에 첨부하고 싶을 때 유용합니다. fromData 메서드는 원시 데이터를 반환하는 클로저와 첨부파일에 지정할 이름을 인자로 받습니다.
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @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 메서드를 사용하면 이미지를 인라인으로 삽입할 수 있습니다. $message 변수는 Laravel이 모든 메일 템플릿에 자동으로 주입해주므로, 따로 전달할 필요는 없습니다.
<body>
Here is an image:
<img src="{{ $message->embed($pathToImage) }}">
</body>WARNING
$message 변수는 일반 텍스트(plain-text) 메시지 템플릿에서는 사용할 수 없습니다. 텍스트 메시지는 인라인 첨부파일 기능 자체를 지원하지 않기 때문입니다.
원시 데이터로 인라인 이미지 삽입하기
이미 원시 이미지 데이터 문자열을 가지고 있고 이를 메일 템플릿에 삽입하고 싶다면, $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
{
/**
* 모델의 첨부파일 표현을 가져옵니다.
*/
public function toMailAttachment(): Attachment
{
return Attachment::fromPath('/path/to/file');
}
}Attachable 객체를 정의했다면, 메일 메시지를 구성할 때 attachments 메서드에서 해당 객체의 인스턴스를 그대로 반환하면 됩니다.
/**
* 메시지에 첨부할 파일 목록을 가져옵니다.
*
* @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;
/**
* 메시지 헤더를 가져옵니다.
*/
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와 같은 일부 서드파티 메일 서비스는 메시지 "태그(tag)"와 "메타데이터(metadata)" 기능을 지원합니다. 이 기능을 이용하면 애플리케이션에서 발송한 메일을 그룹으로 묶거나 추적할 수 있습니다. Envelope 정의를 통해 메일 메시지에 태그와 메타데이터를 추가할 수 있습니다.
use Illuminate\Mail\Mailables\Envelope;
/**
* 메시지 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;
/**
* 메시지 envelope를 가져옵니다.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Order Shipped',
using: [
function (Email $message) {
// ...
},
]
);
}Markdown Mailable
Markdown mailable를 사용하면 메일 알림에서 사용하는 미리 만들어진 템플릿과 컴포넌트를 mailable에서도 그대로 활용할 수 있습니다. 메시지를 Markdown으로 작성하면 Laravel이 이를 반응형 HTML 템플릿으로 렌더링해주는 동시에 일반 텍스트 버전까지 자동으로 생성해줍니다. 즉, HTML과 텍스트 버전을 따로 관리할 필요 없이 Markdown 파일 하나만 작성하면 됩니다.
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;
/**
* 메시지 콘텐츠 정의를 가져옵니다.
*/
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>
<h1 id="queueing-by-default">주문이 발송되었습니다</h1>
주문하신 상품이 발송되었습니다!
<x-mail::button :url="$url">
주문 확인하기
</x-mail::button>
감사합니다,<br>
{{ config('app.name') }}
</x-mail::message>NOTE
Markdown 메일을 작성할 때는 불필요한 들여쓰기를 사용하지 마세요. Markdown 표준에 따라 들여쓰기된 콘텐츠는 코드 블록으로 렌더링됩니다.
버튼 컴포넌트
버튼 컴포넌트는 중앙에 배치된 버튼 링크를 렌더링합니다. 이 컴포넌트는 url과 선택적으로 color 두 가지 인자를 받습니다. 지원되는 색상은 primary, success, error입니다. 하나의 메시지에 원하는 만큼 버튼 컴포넌트를 추가할 수 있습니다:
<x-mail::button :url="$url" color="success">
주문 확인하기
</x-mail::button>패널 컴포넌트
패널 컴포넌트는 주어진 텍스트 블록을 나머지 메시지와 살짝 다른 배경색을 가진 패널 안에 렌더링합니다. 특정 텍스트 블록을 강조하고 싶을 때 유용합니다:
<x-mail::panel>
이 부분은 패널 콘텐츠입니다.
</x-mail::panel>테이블 컴포넌트
테이블 컴포넌트를 사용하면 Markdown 표를 HTML 테이블로 변환할 수 있습니다. 이 컴포넌트는 Markdown 표 문법을 그대로 콘텐츠로 받아들이며, 기본 Markdown 표 정렬 문법을 사용해 컬럼 정렬도 지원합니다:
<x-mail::table>
| Laravel | Table | Example |
| ------------- | :-----------: | ------------: |
| 2번 컬럼은 | 중앙 정렬 | $10 |
| 3번 컬럼은 | 우측 정렬 | $20 |
</x-mail::table>컴포넌트 커스터마이징
Markdown 메일 컴포넌트를 애플리케이션으로 내보내(export) 원하는 대로 커스터마이징할 수 있습니다. 컴포넌트를 내보내려면 vendor:publish Artisan 명령어로 laravel-mail 에셋 태그를 퍼블리시하면 됩니다:
php artisan vendor:publish --tag=laravel-mail이 명령어를 실행하면 Markdown 메일 컴포넌트가 resources/views/vendor/mail 디렉터리에 퍼블리시됩니다. mail 디렉터리 안에는 html과 text 디렉터리가 있으며, 각각 사용 가능한 모든 컴포넌트에 대한 HTML 버전과 텍스트 버전이 담겨 있습니다. 이 컴포넌트들은 원하는 대로 자유롭게 수정할 수 있습니다.
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 속성을 자동으로 사용해 수신자를 결정하므로, 해당 객체에 이 속성들이 존재하는지 미리 확인해 두어야 합니다. 수신자를 지정한 뒤에는 작성해 둔 메일러블 클래스의 인스턴스를 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));여러 수신자에게 반복 발송하기
수신자 배열이나 이메일 주소 목록을 순회하면서 메일러블을 발송해야 하는 경우가 종종 있습니다. 그런데 to 메서드는 호출될 때마다 메일러블의 수신자 목록에 이메일 주소를 추가하는 방식으로 동작합니다. 따라서 반복문 안에서 같은 메일러블 인스턴스를 재사용하면, 반복할 때마다 이전에 추가된 모든 수신자에게 메일이 중복 발송되는 문제가 생깁니다. 그러므로 반드시 수신자마다 메일러블 인스턴스를 새로 생성해야 합니다:
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));이 메서드를 호출하면 Laravel이 자동으로 큐에 Job을 등록해서 백그라운드에서 메일을 발송해 줍니다. 이 기능을 사용하려면 먼저 큐를 설정해 두어야 합니다.
지연 발송 큐잉
큐에 등록한 이메일 메시지의 발송 시점을 지연시키고 싶다면 later 메서드를 사용하면 됩니다. later 메서드의 첫 번째 인자로는 메시지가 언제 발송되어야 하는지를 나타내는 DateTime 인스턴스를 전달합니다:
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->later(now()->plus(minutes: 10), new OrderShipped($order));특정 큐로 등록하기
make:mail 명령어로 생성한 모든 메일러블 클래스는 Illuminate\Bus\Queueable 트레이트를 사용하므로, 메일러블 인스턴스에서 onQueue와 onConnection 메서드를 호출해 메시지를 처리할 커넥션과 큐 이름을 지정할 수 있습니다:
$message = (new OrderShipped($order))
->onConnection('sqs')
->onQueue('emails');
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->queue($message);또는 메일러블 클래스에 Connection, Queue 속성(Attribute)을 지정해서 커넥션과 큐를 설정할 수도 있습니다:
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;
#[Connection('sqs')]
#[Queue('emails')]
class OrderShipped extends Mailable
{
// ...
}항상 큐로 처리하기
특정 메일러블 클래스는 항상 큐에 등록되도록 만들고 싶다면, 해당 클래스에 ShouldQueue 계약(Contract)을 구현하면 됩니다. 이렇게 하면 메일을 보낼 때 send 메서드를 호출하더라도, 해당 클래스가 ShouldQueue 계약을 구현하고 있기 때문에 Laravel이 자동으로 큐에 등록해서 처리해 줍니다:
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShipped extends Mailable implements ShouldQueue
{
// ...
}큐에 등록된 메일러블과 데이터베이스 트랜잭션
큐에 등록된 메일러블이 데이터베이스 트랜잭션 내에서 디스패치되면, 해당 트랜잭션이 커밋되기 전에 큐 워커가 먼저 Job을 처리해 버릴 수 있습니다. 이 경우 트랜잭션 안에서 모델이나 데이터베이스 레코드에 가한 변경 사항이 아직 데이터베이스에 반영되지 않은 상태일 수 있습니다. 마찬가지로 트랜잭션 내에서 새로 생성한 모델이나 레코드도 아직 데이터베이스에 존재하지 않을 수 있습니다. 만약 메일러블이 이런 모델에 의존하고 있다면, 큐에 등록된 메일이 처리되는 시점에 예기치 못한 오류가 발생할 수 있습니다.
사용 중인 큐 커넥션의 after_commit 설정값이 false로 되어 있더라도, 메일 발송 시 afterCommit 메서드를 호출하면 해당 메일러블만큼은 열려 있는 모든 데이터베이스 트랜잭션이 커밋된 이후에 디스패치되도록 지정할 수 있습니다:
Mail::to($request->user())->send(
(new OrderShipped($order))->afterCommit()
);또는 메일러블의 생성자에서 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
이와 관련된 문제를 해결하는 방법에 대해 더 자세히 알고 싶다면 큐 작업과 데이터베이스 트랜잭션 문서를 참고하세요.
큐에 등록된 메일 발송 실패 처리
큐에 등록된 이메일 발송이 실패하면, 메일러블 클래스에 정의되어 있는 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
{
// ...
}
}메일
메일러블 렌더링하기
실제로 메일을 발송하지 않고 메일러블의 HTML 콘텐츠만 확인하고 싶을 때가 있습니다. 이럴 때는 메일러블의 render 메서드를 호출하면 됩니다. 이 메서드는 메일러블을 평가(evaluate)한 결과인 HTML 콘텐츠를 문자열 형태로 반환합니다:
use App\Mail\InvoicePaid;
use App\Models\Invoice;
$invoice = Invoice::find(1);
return (new InvoicePaid($invoice))->render();브라우저에서 메일러블 미리보기
메일러블의 템플릿을 디자인하다 보면, 일반적인 Blade 템플릿을 다루듯 브라우저에서 바로 렌더링 결과를 확인하고 싶을 때가 많습니다. 이런 이유로 Laravel에서는 라우트 클로저나 컨트롤러에서 메일러블 인스턴스를 그대로 반환할 수 있도록 허용합니다. 메일러블을 반환하면 실제로 메일을 발송하지 않고도 브라우저에서 렌더링된 화면을 바로 확인할 수 있어, 디자인 작업 시 발송 없이 빠르게 미리보기를 할 수 있습니다:
Route::get('/mailable', function () {
$invoice = App\Models\Invoice::find(1);
return new App\Mail\InvoicePaid($invoice);
});NOTE
실제 이메일 클라이언트마다 CSS 렌더링 방식이 조금씩 다르기 때문에, 브라우저 미리보기가 완벽히 동일하다고 보장할 수는 없습니다. 다양한 클라이언트에서의 실제 렌더링 결과까지 확인하고 싶다면 Mailtrap이나 HTML Email Check 같은 외부 도구를 함께 활용하는 것도 좋은 방법입니다.
메일
메일러블 다국어 처리(로케일)
Laravel에서는 요청의 현재 로케일과 다른 언어로 메일러블을 전송할 수 있습니다. 심지어 메일을 큐에 등록하는 경우에도 지정한 로케일 정보가 그대로 유지됩니다.
이를 위해 Mail 파사드는 원하는 언어를 지정할 수 있는 locale 메서드를 제공합니다. 메일러블의 템플릿이 렌더링되는 동안에는 애플리케이션이 이 로케일로 전환되며, 렌더링이 끝나면 다시 이전 로케일로 되돌아갑니다:
Mail::to($request->user())->locale('es')->send(
new OrderShipped($order)
);사용자가 선호하는 로케일
애플리케이션에서 각 사용자별로 선호하는 로케일을 저장해두는 경우가 있습니다. 이럴 때는 모델에 HasLocalePreference 계약(contract)을 구현해두면, Laravel이 메일을 보낼 때 저장된 로케일 정보를 자동으로 사용하도록 만들 수 있습니다:
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Model implements HasLocalePreference
{
/**
* 사용자가 선호하는 로케일을 반환합니다.
*/
public function preferredLocale(): string
{
return $this->locale;
}
}이 인터페이스를 구현해두면, 해당 모델에게 메일러블이나 알림을 전송할 때 Laravel이 자동으로 선호 로케일을 사용합니다. 따라서 이 인터페이스를 사용하는 경우에는 별도로 locale 메서드를 호출할 필요가 없습니다:
Mail::to($request->user())->send(new OrderShipped($order));NOTE
예를 들어 한국어 서비스를 운영하면서 일부 사용자에게는 영어 안내 메일을 보내야 하는 다국어 서비스를 만든다면, 사용자 모델에 locale 컬럼을 두고 preferredLocale 메서드로 반환하도록 구현해두는 방식을 추천합니다. 이렇게 하면 메일을 보낼 때마다 로케일을 신경 쓸 필요 없이 항상 사용자에게 맞는 언어로 메일이 발송됩니다.
테스트
Mailable 콘텐츠 테스트하기
라라벨은 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" 관련 assertion은 Mailable의 HTML 버전에 특정 문자열이 포함되어 있는지 검사하고, "text" 관련 assertion은 일반 텍스트 버전에 해당 문자열이 포함되어 있는지 검사합니다.
Mailable 발송 테스트하기
Mailable의 콘텐츠를 검증하는 테스트와, 특정 사용자에게 특정 Mailable이 "발송되었는지" 검증하는 테스트는 서로 분리해서 작성하는 것을 권장합니다. 보통 발송 로직을 테스트할 때는 Mailable의 실제 내용까지 검증할 필요는 없으며, 라라벨이 해당 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();
// 주문 배송 처리 로직 실행...
// 어떤 메일도 발송되지 않았는지 확인...
Mail::assertNothingSent();
// 메일이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class);
// 메일이 두 번 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 2);
// 특정 이메일 주소로 메일이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// 여러 이메일 주소로 메일이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// 메일이 발송되지 않았는지 확인...
Mail::assertNotSent(AnotherMailable::class);
// 메일이 두 번 발송되었는지 확인...
Mail::assertSentTimes(OrderShipped::class, 2);
// 메일이 정확히 한 번만 발송되었는지 확인...
Mail::assertSentOnce(OrderShipped::class);
// 총 3개의 메일이 발송되었는지 확인...
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();
// 주문 배송 처리 로직 실행...
// 어떤 메일도 발송되지 않았는지 확인...
Mail::assertNothingSent();
// 메일이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class);
// 메일이 두 번 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 2);
// 특정 이메일 주소로 메일이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// 여러 이메일 주소로 메일이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// 메일이 발송되지 않았는지 확인...
Mail::assertNotSent(AnotherMailable::class);
// 메일이 두 번 발송되었는지 확인...
Mail::assertSentTimes(OrderShipped::class, 2);
// 메일이 정확히 한 번만 발송되었는지 확인...
Mail::assertSentOnce(OrderShipped::class);
// 총 3개의 메일이 발송되었는지 확인...
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 메서드에는 클로저를 전달할 수 있습니다. 이렇게 하면 주어진 "조건 검사"를 통과하는 Mailable이 발송(또는 큐에 등록)되었는지 확인할 수 있습니다. 조건을 만족하는 Mailable이 하나라도 발송되었다면 이 assertion은 성공합니다.
Mail::assertSent(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});Mail 파사드의 assertion 메서드를 호출할 때, 전달한 클로저가 받는 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;
});NOTE
Mailable 콘텐츠 검증용 assertion(assertSeeInHtml 등)과 발송 여부 검증용 assertion(Mail::assertSent 등)은 성격이 다릅니다. 전자는 Mailable 인스턴스를 직접 생성해서 그 내용을 검사하는 반면, 후자는 Mail::fake()로 실제 발송을 가로챈 뒤 애플리케이션 코드가 올바른 Mailable을 올바른 대상에게 보내도록 지시했는지를 검사합니다. 두 가지를 함께 활용하면 "메일 내용이 맞는지"와 "메일이 제대로 트리거되는지"를 각각 독립적으로 테스트할 수 있습니다.
메일
메일과 로컬 개발 환경
메일 기능이 있는 애플리케이션을 개발할 때, 실제 이메일 주소로 진짜 메일을 발송하고 싶지는 않을 것입니다. Laravel은 로컬 개발 중에 실제 메일 발송을 "비활성화"할 수 있는 몇 가지 방법을 제공합니다.
로그 드라이버
log 메일 드라이버를 사용하면 실제로 메일을 발송하는 대신 모든 메일 메시지를 로그 파일에 기록하여 확인할 수 있습니다. 이 드라이버는 보통 로컬 개발 환경에서만 사용됩니다. 환경별로 애플리케이션 설정을 구성하는 방법에 대해서는 설정 문서를 참고하세요.
HELO / Mailtrap / Mailpit
또는 HELO나 Mailtrap 같은 서비스와 smtp 드라이버를 함께 사용해서, 실제 메일 클라이언트로 확인할 수 있는 "더미" 메일함으로 메일을 발송하는 방법도 있습니다. 이 방식은 Mailtrap의 메시지 뷰어에서 실제 발송될 최종 이메일의 모습을 직접 눈으로 확인할 수 있다는 장점이 있습니다.
Laravel Sail을 사용 중이라면 Mailpit으로 메일을 미리 볼 수 있습니다. Sail이 실행 중일 때 http://localhost:8025 주소로 Mailpit 인터페이스에 접속할 수 있습니다.
전역 to 주소 사용하기
마지막으로, Mail 파사드가 제공하는 alwaysTo 메서드를 호출해서 모든 메일이 발송될 전역 "to" 주소를 지정할 수도 있습니다. 이 메서드는 보통 애플리케이션의 서비스 프로바이더 중 하나의 boot 메서드 안에서 호출합니다.
use Illuminate\Support\Facades\Mail;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
if ($this->app->environment('local')) {
Mail::alwaysTo('taylor@example.com');
}
}NOTE
alwaysTo 메서드를 사용하면 메일 메시지에 지정된 "cc"나 "bcc" 주소는 모두 제거되고, 오직 지정한 to 주소로만 메일이 발송됩니다. 로컬 환경에서 실수로 외부 주소에 메일이 발송되는 것을 막고 싶을 때 특히 유용합니다.
이벤트
Laravel은 메일 메시지를 전송하는 과정에서 두 가지 이벤트를 발생시킵니다. MessageSending 이벤트는 메시지가 전송되기 전에 발생하고, MessageSent 이벤트는 메시지가 전송된 후에 발생합니다. 여기서 주의할 점은, 이 이벤트들은 메일이 큐에 등록되는 시점이 아니라 실제로 전송되는 시점에 발생한다는 것입니다.
애플리케이션 내에서 이 이벤트들을 처리할 이벤트 리스너를 다음과 같이 작성할 수 있습니다:
use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;
class LogMessage
{
/**
* 이벤트를 처리합니다.
*/
public function handle(MessageSending $event): void
{
// ...
}
}NOTE
메일이 큐를 통해 전송되는 경우, 큐 작업이 실행될 때 위 이벤트들이 발생합니다. 메일 전송 자체가 언제 이루어지는지 헷갈리지 않도록 주의하세요. 큐에 등록되는 시점을 감지하고 싶다면 큐 관련 이벤트(예: JobProcessing)를 활용해야 합니다.
커스텀 트랜스포트
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
{
/**
* 새로운 Mailchimp 트랜스포트 인스턴스를 생성합니다.
*/
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(),
]]);
}
/**
* 트랜스포트를 문자열로 표현합니다.
*/
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;
/**
* 애플리케이션의 서비스를 부트스트랩합니다.
*/
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에서 관리하는 다른 추가 트랜스포트를 사용하고 싶을 수도 있습니다. 이럴 때는 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;
/**
* 애플리케이션의 서비스를 부트스트랩합니다.
*/
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',
// ...
],NOTE
위 예시처럼 커스텀 트랜스포트나 추가 Symfony 트랜스포트를 등록할 때는, 필요한 Composer 패키지가 실제로 설치되어 있는지, 그리고 API 키와 같은 자격 증명이 .env 파일과 config/services.php에 올바르게 설정되어 있는지 먼저 확인하는 것이 좋습니다.