메일
업데이트됨번역일: 2026년 8월 2일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 8월 2일
- 번역 갱신
- 2026년 8월 2일
메일
- 소개
- Mailable 생성
- Mailable 작성
- Markdown Mailable
- 메일 발송
- Mailable 렌더링
- Mailable 현지화
- 테스트
- 메일 및 로컬 개발
- 이벤트
- 커스텀 트랜스포트
메일
목차
소개
이메일 발송은 생각보다 복잡하지 않습니다. Laravel은 널리 사용되는 Symfony Mailer 컴포넌트를 기반으로 한 깔끔하고 직관적인 이메일 API를 제공합니다. SMTP, Cloudflare, Mailgun, Postmark, Resend, Amazon SES, sendmail 등 다양한 전송 드라이버를 지원하므로, 로컬 환경이나 클라우드 서비스를 통해 빠르게 메일 발송을 시작할 수 있습니다.
설정
이메일 관련 설정은 config/mail.php 파일에서 관리합니다. 이 파일에는 여러 개의 mailer를 정의할 수 있으며, 각 mailer는 고유한 설정과 전송 방식(transport)을 가질 수 있습니다. 예를 들어, 트랜잭션 메일(주문 확인, 비밀번호 재설정 등)은 Postmark로 발송하고, 대량 메일은 Amazon SES로 발송하는 식으로 용도에 맞게 분리할 수 있습니다.
config/mail.php 파일 내 mailers 배열에는 Laravel이 지원하는 주요 드라이버별 샘플 설정이 포함되어 있습니다. default 값을 통해 기본으로 사용할 mailer를 지정합니다.
드라이버 / 전송 방식 사전 준비
Mailgun, Postmark, Resend와 같은 API 기반 드라이버는 SMTP 방식보다 일반적으로 더 간단하고 빠릅니다. 가능하다면 이러한 API 기반 드라이버 사용을 권장합니다.
Cloudflare 드라이버
Cloudflare 드라이버를 사용하려면 Composer로 Symfony HTTP Client를 설치합니다.
composer require symfony/http-client그런 다음 config/mail.php에서 두 가지를 수정합니다. 먼저 기본 mailer를 cloudflare로 지정합니다.
'default' => env('MAIL_MAILER', 'cloudflare'),그리고 mailers 배열에 다음 설정을 추가합니다.
'cloudflare' => [
'transport' => 'cloudflare',
],이후 config/services.php에 Cloudflare 인증 정보를 추가합니다.
'cloudflare' => [
'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
'key' => env('CLOUDFLARE_KEY'),
],Mailgun 드라이버
Mailgun 드라이버를 사용하려면 Composer로 관련 패키지를 설치합니다.
composer require symfony/mailgun-mailer symfony/http-clientconfig/mail.php에서 기본 mailer를 mailgun으로 지정합니다.
'default' => env('MAIL_MAILER', 'mailgun'),그리고 mailers 배열에 다음 설정을 추가합니다.
'mailgun' => [
'transport' => 'mailgun',
// 'client' => [
// 'timeout' => 5,
// ],
],이후 config/services.php에 Mailgun 인증 정보를 추가합니다.
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
'scheme' => 'https',
],미국 외 지역의 Mailgun 리전을 사용하는 경우(예: EU 리전), endpoint를 해당 리전의 주소로 변경합니다.
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),
'scheme' => 'https',
],Postmark 드라이버
Postmark 드라이버를 사용하려면 Composer로 관련 패키지를 설치합니다.
composer require symfony/postmark-mailer symfony/http-clientconfig/mail.php의 default 옵션을 postmark로 설정한 후, config/services.php에 API 키를 추가합니다.
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],특정 mailer에서 사용할 Postmark 메시지 스트림을 지정하려면 config/mail.php의 mailer 설정 배열에 message_stream_id를 추가합니다.
'postmark' => [
'transport' => 'postmark',
'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
// 'client' => [
// 'timeout' => 5,
// ],
],이 방식을 활용하면 메시지 스트림이 다른 여러 개의 Postmark mailer를 구성하는 것도 가능합니다.
Resend 드라이버
Resend 드라이버를 사용하려면 Composer로 Resend PHP SDK를 설치합니다.
composer require resend/resend-phpconfig/mail.php의 default 옵션을 resend로 설정한 후, config/services.php에 API 키를 추가합니다.
'resend' => [
'key' => env('RESEND_API_KEY'),
],SES 드라이버
Amazon SES 드라이버를 사용하려면 AWS SDK for PHP를 Composer로 설치합니다.
composer require aws/aws-sdk-phpconfig/mail.php의 default 옵션을 ses로 설정하고, config/services.php에 AWS 인증 정보를 추가합니다.
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],AWS 임시 자격 증명(세션 토큰)을 사용하는 경우 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 헤더를 반환합니다.
/**
* 메시지 헤더를 반환합니다.
*/
public function headers(): Headers
{
return new Headers(
text: [
'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic',
],
);
}SES 테넌트를 통해 이메일을 발송하려면, headers 메서드에서 X-Ses-Tenant-Name 헤더를 반환합니다. Laravel은 이 헤더 값을 TenantName 옵션으로 SES에 전달합니다.
public function headers(): Headers
{
return new Headers(
text: [
'X-Ses-Tenant-Name' => 'tenant-id',
],
);
}이메일 발송 시 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'],
],
],
],페일오버 설정
외부 메일 서비스가 일시적으로 장애가 발생하는 상황에 대비해, 기본 mailer가 실패했을 때 자동으로 백업 mailer로 전환하는 페일오버(failover) 구성을 설정할 수 있습니다.
config/mail.php의 mailers 배열에 failover 트랜스포트를 사용하는 mailer를 정의합니다. mailers 배열 안에 시도할 mailer를 우선순위 순서대로 나열합니다.
'mailers' => [
'failover' => [
'transport' => 'failover',
'mailers' => [
'postmark',
'mailgun',
'sendmail',
],
'retry_after' => 60,
],
// ...
],설정이 완료되면 .env 파일에서 기본 mailer를 failover로 지정합니다.
MAIL_MAILER=failoverNOTE
페일오버는 **고가용성(High Availability)**을 목적으로 합니다. 기본 mailer가 정상이라면 항상 첫 번째 mailer만 사용됩니다.
라운드 로빈 설정
roundrobin 트랜스포트를 사용하면 여러 mailer에 발송 부하를 분산할 수 있습니다. config/mail.php의 mailers 배열에 다음과 같이 정의합니다.
'mailers' => [
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
// ...
],설정 후 config/mail.php의 default 값을 roundrobin으로 지정합니다.
'default' => env('MAIL_MAILER', 'roundrobin'),라운드 로빈 트랜스포트는 설정된 mailer 목록에서 무작위로 첫 번째 mailer를 선택한 뒤, 이후 메일을 보낼 때마다 순서대로 다음 mailer로 전환합니다.
페일오버가 **고가용성(특정 mailer 장애 대응)**을 목적으로 한다면, 라운드 로빈은 **부하 분산(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메서드:Illuminate\Mail\Mailables\Content객체를 반환하며, 메일 본문을 렌더링할 Blade 템플릿을 지정합니다.attachments메서드: 첨부 파일 배열을 반환합니다.
발신자 설정
Envelope으로 발신자 지정
발신자("보내는 사람")를 설정하는 방법은 두 가지입니다. 첫 번째는 envelope 메서드 안에서 직접 from 주소를 지정하는 방식입니다.
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
/**
* 메시지 Envelope을 반환합니다.
*/
public function envelope(): Envelope
{
return new Envelope(
from: new Address('no-reply@myapp.kr', '내 서비스'),
subject: '주문이 발송되었습니다',
);
}답장 주소(replyTo)를 별도로 설정하고 싶다면 다음과 같이 추가합니다.
return new Envelope(
from: new Address('no-reply@myapp.kr', '내 서비스'),
replyTo: [
new Address('support@myapp.kr', '고객센터'),
],
subject: '주문이 발송되었습니다',
);전역 `from` 주소 사용
애플리케이션의 모든 메일이 동일한 발신자 주소를 사용한다면, 매 Mailable 클래스마다 from을 반복해서 지정하는 것은 번거롭습니다. 이런 경우 config/mail.php에 전역 발신자를 설정해두면, 개별 Mailable에 from이 없을 때 자동으로 이 값이 사용됩니다.
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],마찬가지로 전역 reply_to 주소도 설정할 수 있습니다.
'reply_to' => [
'address' => 'support@example.com',
'name' => '앱 이름',
],뷰 설정
content 메서드에서 view 옵션을 통해 메일 본문을 렌더링할 Blade 템플릿을 지정합니다. Blade의 모든 기능(조건문, 반복문, 컴포넌트 등)을 그대로 활용할 수 있습니다.
/**
* 메시지 콘텐츠 정의를 반환합니다.
*/
public function content(): Content
{
return new Content(
view: 'mail.orders.shipped',
);
}NOTE
메일 전용 템플릿은 resources/views/mail 디렉터리에 모아두면 관리하기 편리합니다. 물론 resources/views 안의 어느 위치에 두어도 무방합니다.
일반 텍스트(Plain Text) 메일
HTML 버전 외에 일반 텍스트 버전을 함께 제공하면, HTML을 지원하지 않는 메일 클라이언트에서도 내용을 올바르게 표시할 수 있습니다. Content 정의 시 text 파라미터로 텍스트 전용 템플릿을 지정하세요.
/**
* 메시지 콘텐츠 정의를 반환합니다.
*/
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 프로퍼티를 통한 전달
메일 템플릿에 데이터를 전달하는 가장 간단한 방법은 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',
);
}
}이렇게 하면 Blade 템플릿에서 $order 변수를 곧바로 사용할 수 있습니다.
<div>
주문 금액: {{ $order->price }}
</div>`with` 파라미터를 통한 전달
템플릿에 전달하기 전에 데이터를 가공하거나, 변수명을 바꾸고 싶을 때는 with 파라미터를 사용합니다. 이 경우 생성자에서 받은 데이터를 protected 또는 private으로 선언해 템플릿에 자동 노출되지 않도록 한 뒤, with으로 필요한 값만 선택해서 넘깁니다.
<?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>
주문 금액: {{ $orderPrice }}
</div>첨부 파일
메일에 파일을 첨부하려면 attachments 메서드에서 Attachment 인스턴스 배열을 반환합니다. 파일 경로를 직접 지정할 때는 Attachment::fromPath를 사용합니다.
use Illuminate\Mail\Mailables\Attachment;
/**
* 메시지의 첨부 파일 목록을 반환합니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file'),
];
}파일 이름과 MIME 타입을 커스터마이즈하려면 as와 withMime 메서드를 체이닝합니다.
/**
* 메시지의 첨부 파일 목록을 반환합니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file')
->as('견적서.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('견적서.pdf')
->withMime('application/pdf'),
];
}기본 디스크가 아닌 특정 디스크(예: S3)를 사용하려면 fromStorageDisk를 사용합니다.
/**
* 메시지의 첨부 파일 목록을 반환합니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromStorageDisk('s3', '/path/to/file')
->as('견적서.pdf')
->withMime('application/pdf'),
];
}원시 데이터(Raw Data) 첨부
메모리에서 생성한 PDF처럼 디스크에 저장하지 않은 데이터를 첨부할 때는 fromData 메서드를 사용합니다. 첫 번째 인수로 클로저를 받아 원시 바이트 데이터를 반환하고, 두 번째 인수로 첨부 파일 이름을 지정합니다.
/**
* 메시지의 첨부 파일 목록을 반환합니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [
Attachment::fromData(fn () => $this->pdf, '보고서.pdf')
->withMime('application/pdf'),
];
}인라인 첨부(이미지 삽입)
메일 본문에 이미지를 직접 삽입하고 싶을 때는 Blade 템플릿에서 $message->embed() 메서드를 사용합니다. Laravel은 모든 메일 템플릿에 $message 변수를 자동으로 주입하므로 별도로 전달할 필요가 없습니다.
<body>
아래는 첨부 이미지입니다:
<img src="{{ $message->embed($pathToImage) }}">
</body>WARNING
$message 변수는 일반 텍스트(plain-text) 템플릿에서는 사용할 수 없습니다. 텍스트 메일은 인라인 첨부를 지원하지 않기 때문입니다.
원시 이미지 데이터 삽입
이미 메모리에 이미지 데이터가 있다면 embedData 메서드로 직접 삽입할 수 있습니다. 파일 이름도 함께 지정해야 합니다.
<body>
원시 데이터로부터 생성된 이미지입니다:
<img src="{{ $message->embedData($data, 'example-image.jpg') }}">
</body>Attachable 객체
단순히 경로 문자열로 파일을 첨부하는 것도 충분하지만, 실제 프로젝트에서는 첨부할 대상이 모델이나 객체로 표현되는 경우가 많습니다. 예를 들어 Photo 모델이 있다면, 경로를 꺼내는 대신 모델 자체를 첨부 파일로 전달할 수 있으면 훨씬 편리합니다. Attachable 객체가 바로 이런 용도로 만들어진 기능입니다.
첨부 가능 객체를 만들려면 해당 클래스에 Illuminate\Contracts\Mail\Attachable 인터페이스를 구현하고, toMailAttachment 메서드에서 Illuminate\Mail\Attachment 인스턴스를 반환하면 됩니다.
<?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');
}
}이렇게 구현하면 attachments 메서드에서 모델 인스턴스를 그대로 반환할 수 있습니다.
/**
* 메시지의 첨부 파일 목록을 반환합니다.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [$this->photo];
}파일이 Amazon S3 같은 원격 스토리지에 저장된 경우에도 동일하게 활용할 수 있습니다.
// 기본 디스크의 파일로 첨부 생성
return Attachment::fromStorage($this->path);
// 특정 디스크의 파일로 첨부 생성
return Attachment::fromStorageDisk('backblaze', $this->path);메모리상의 데이터로 첨부 객체를 만들 수도 있습니다.
return Attachment::fromData(fn () => $this->content, '사진 이름');as와 withMime으로 파일명과 MIME 타입을 커스터마이즈하는 것도 동일하게 지원합니다.
return Attachment::fromPath('/path/to/file')
->as('사진 이름')
->withMime('image/jpeg');헤더 커스터마이즈
발송 메일에 커스텀 헤더를 추가해야 할 때는 Mailable 클래스에 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: '주문이 발송되었습니다',
tags: ['shipment'],
metadata: [
'order_id' => $this->order->id,
],
);
}- Mailgun 사용 시: 태그 및 메타데이터 공식 문서를 참고하세요.
- Postmark 사용 시: 태그 및 메타데이터 공식 문서를 참고하세요.
- Amazon SES 사용 시:
metadata를 통해 SES "태그"를 설정합니다.
Symfony Message 커스터마이즈
Laravel의 메일 기능은 내부적으로 Symfony Mailer를 기반으로 동작합니다. 메일 발송 직전에 Symfony의 Email 객체를 직접 조작하고 싶다면 Envelope의 using 파라미터에 콜백을 등록하면 됩니다.
use Illuminate\Mail\Mailables\Envelope;
use Symfony\Component\Mime\Email;
/**
* 메시지 Envelope을 반환합니다.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: '주문이 발송되었습니다',
using: [
function (Email $message) {
// Symfony Email 객체를 직접 조작합니다.
},
]
);
}메일
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;
/**
* 메시지 콘텐츠 정의를 반환합니다.
*/
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="customizing-the-components">주문이 발송되었습니다</h1>
주문하신 상품이 발송되었습니다!
<x-mail::button :url="$url">
주문 확인하기
</x-mail::button>
감사합니다,<br>
{{ config('app.name') }}
</x-mail::message>NOTE
Markdown 이메일을 작성할 때 과도한 들여쓰기를 사용하지 마세요. Markdown 표준에 따라 들여쓰기된 내용은 코드 블록으로 렌더링됩니다.
Button 컴포넌트
Button 컴포넌트는 가운데 정렬된 버튼 링크를 렌더링합니다. url과 선택적 color 두 가지 인수를 받으며, 지원하는 색상은 primary, success, error입니다. 하나의 메시지에 버튼을 여러 개 추가할 수도 있습니다.
<x-mail::button :url="$url" color="success">
주문 확인하기
</x-mail::button>Panel 컴포넌트
Panel 컴포넌트는 메시지의 나머지 영역과 약간 다른 배경색을 가진 패널 안에 텍스트 블록을 렌더링합니다. 특정 내용을 강조하고 싶을 때 유용합니다.
<x-mail::panel>
이 내용은 패널 안에 표시됩니다.
</x-mail::panel>Table 컴포넌트
Table 컴포넌트를 사용하면 Markdown 테이블을 HTML 테이블로 변환할 수 있습니다. Markdown 테이블 정렬 문법을 그대로 사용할 수 있어 열 정렬도 지원됩니다.
<x-mail::table>
| 항목 | 수량 | 금액 |
| ------------- | :-----------: | ------------: |
| 상품 A | 1 | ₩10,000 |
| 상품 B | 2 | ₩20,000 |
</x-mail::table>컴포넌트 커스터마이징
Markdown 메일 컴포넌트를 직접 수정하고 싶다면, 먼저 애플리케이션으로 export해야 합니다. vendor:publish Artisan 명령어에 laravel-mail 태그를 지정해 실행하면 됩니다.
php artisan vendor:publish --tag=laravel-mail이 명령어를 실행하면 Markdown 메일 컴포넌트 파일들이 resources/views/vendor/mail 디렉토리에 복사됩니다. 이 디렉토리 안에는 html과 text 두 개의 하위 디렉토리가 생성되며, 각각 모든 컴포넌트의 HTML 버전과 텍스트 버전이 포함됩니다. 이 파일들을 원하는 대로 자유롭게 수정할 수 있습니다.
CSS 커스터마이징
컴포넌트를 export하고 나면 resources/views/vendor/mail/html/themes 디렉토리에 default.css 파일이 생성됩니다. 이 파일의 CSS를 수정하면 스타일이 Markdown 메일 메시지의 HTML 표현에 인라인 CSS로 자동 변환되어 적용됩니다.
완전히 새로운 테마를 만들고 싶다면, html/themes 디렉토리에 새 CSS 파일을 추가하세요. 파일을 저장한 후, config/mail.php 설정 파일의 theme 옵션을 새 테마 파일명으로 변경하면 됩니다.
특정 Mailable에만 별도의 테마를 적용하고 싶다면, 해당 Mailable 클래스의 $theme 프로퍼티에 사용할 테마 이름을 설정하면 됩니다.
메일 발송
메일 전송하기
메일을 보낼 때는 Mail 파사드의 to 메서드를 사용합니다. to 메서드에는 이메일 주소 문자열, 사용자 인스턴스, 또는 사용자 컬렉션을 전달할 수 있습니다. 객체나 컬렉션을 전달하면 라라벨이 해당 객체의 email과 name 속성을 자동으로 읽어 수신자를 지정하므로, 해당 속성이 객체에 존재하는지 확인하세요. 수신자를 지정한 뒤, send 메서드에 Mailable 클래스 인스턴스를 전달하면 됩니다.
<?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');
}
}수신자는 to만으로 제한되지 않습니다. cc(참조)와 bcc(숨은 참조)도 메서드 체이닝으로 함께 지정할 수 있습니다.
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));여러 수신자에게 반복 전송하기
여러 수신자에게 동일한 메일을 반복 전송할 때 주의할 점이 있습니다. to 메서드는 수신자를 누적해서 추가하는 방식으로 동작합니다. 따라서 루프 안에서 같은 Mailable 인스턴스를 재사용하면, 두 번째 반복부터는 이전 수신자까지 포함된 메일이 발송됩니다. 반드시 반복마다 새 인스턴스를 생성하세요.
foreach (['kim@example.com', 'lee@example.com'] as $recipient) {
Mail::to($recipient)->send(new OrderShipped($order));
}특정 Mailer로 전송하기
기본적으로 라라벨은 mail 설정 파일의 default mailer를 사용합니다. 특정 mailer 설정을 직접 지정하고 싶다면 mailer 메서드를 사용하세요.
Mail::mailer('postmark')
->to($request->user())
->send(new OrderShipped($order));메일 큐잉
메일 메시지 큐에 넣기
메일 발송은 응답 시간에 영향을 줄 수 있습니다. 이를 개선하려면 메일을 백그라운드 큐에서 처리하는 방법을 권장합니다. 라라벨의 통합 큐 API를 사용하면 send 대신 queue 메서드만 호출하면 됩니다.
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->queue(new OrderShipped($order));이 메서드는 자동으로 Job을 큐에 등록하여 백그라운드에서 메일을 발송합니다. 이 기능을 사용하려면 먼저 큐를 설정해야 합니다.
메일 발송 지연시키기
큐에 등록된 메일을 특정 시간 이후에 발송하고 싶다면 later 메서드를 사용하세요. 첫 번째 인수로 발송할 시각을 나타내는 DateTime 인스턴스를 전달합니다.
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->later(now()->plus(minutes: 10), new OrderShipped($order));특정 큐와 커넥션 지정하기
make:mail 명령으로 생성된 Mailable 클래스는 Illuminate\Bus\Queueable 트레이트를 사용하므로, onConnection과 onQueue 메서드로 커넥션과 큐 이름을 지정할 수 있습니다.
$message = (new OrderShipped($order))
->onConnection('sqs')
->onQueue('emails');
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->queue($message);또는 Mailable 클래스에 PHP 어트리뷰트를 직접 선언하는 방법도 있습니다.
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;
#[Connection('sqs')]
#[Queue('emails')]
class OrderShipped extends Mailable
{
// ...
}항상 큐로 처리하기
특정 Mailable 클래스를 항상 큐에서 처리하고 싶다면 ShouldQueue 인터페이스를 구현하세요. 이후에는 send 메서드를 호출해도 자동으로 큐에 등록됩니다.
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShipped extends Mailable implements ShouldQueue
{
// ...
}큐 Mailable과 데이터베이스 트랜잭션
데이터베이스 트랜잭션 내에서 큐 Mailable을 디스패치하면, 트랜잭션이 커밋되기 전에 큐 워커가 해당 Job을 먼저 처리할 수 있습니다. 이 경우 트랜잭션 중에 생성하거나 수정한 모델·레코드가 아직 데이터베이스에 반영되지 않은 상태일 수 있으며, Mailable이 해당 데이터에 의존한다면 예기치 않은 오류가 발생할 수 있습니다.
큐 커넥션의 after_commit 옵션이 false로 설정되어 있더라도, 특정 Mailable만 모든 트랜잭션이 커밋된 후에 발송하도록 afterCommit 메서드를 사용할 수 있습니다.
Mail::to($request->user())->send(
(new OrderShipped($order))->afterCommit()
);또는 Mailable 생성자에서 호출하는 방법도 있습니다.
<?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;
/**
* 새 메시지 인스턴스를 생성합니다.
*/
public function __construct()
{
$this->afterCommit();
}
}NOTE
이 문제를 더 자세히 알고 싶다면 큐 Job과 데이터베이스 트랜잭션 문서를 참고하세요.
큐 메일 실패 처리
큐에 등록된 메일 발송이 실패하면, Mailable 클래스에 failed 메서드가 정의되어 있을 경우 해당 메서드가 자동으로 호출됩니다. 실패 원인이 된 Throwable 인스턴스가 인수로 전달됩니다.
<?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;
/**
* 큐 메일 발송 실패를 처리합니다.
*/
public function failed(Throwable $exception): void
{
// 실패 처리 로직 (예: 알림 발송, 로그 기록 등)
}
}메일
Mailable 렌더링
메일을 실제로 발송하지 않고 HTML 콘텐츠만 추출하고 싶을 때가 있습니다. 이럴 때는 Mailable의 render 메서드를 호출하면 됩니다. 이 메서드는 Mailable의 HTML 렌더링 결과를 문자열로 반환합니다.
use App\Mail\InvoicePaid;
use App\Models\Invoice;
$invoice = Invoice::find(1);
return (new InvoicePaid($invoice))->render();브라우저에서 Mailable 미리보기
메일 템플릿을 디자인할 때, 실제 이메일을 발송하지 않고도 브라우저에서 바로 확인할 수 있으면 매우 편리합니다. Laravel에서는 Mailable 인스턴스를 라우트 클로저나 컨트롤러에서 직접 반환할 수 있습니다. 반환된 Mailable은 Blade 템플릿처럼 브라우저에 렌더링되어 표시됩니다.
Route::get('/mailable', function () {
$invoice = App\Models\Invoice::find(1);
return new App\Mail\InvoicePaid($invoice);
});NOTE
이 방법은 개발 환경에서 메일 레이아웃과 내용을 빠르게 확인할 때 유용합니다. 실제 서비스 환경에서는 이 라우트를 노출하지 않도록 주의하세요.
Mailable 로케일 설정
Laravel은 현재 요청의 로케일과 다른 언어로 메일을 발송할 수 있으며, 메일이 큐에 등록된 경우에도 해당 로케일을 기억합니다.
Mail 파사드의 locale 메서드로 원하는 언어를 지정하면, Mailable 템플릿을 렌더링하는 동안 애플리케이션이 해당 로케일로 전환되고, 렌더링이 끝나면 이전 로케일로 자동 복귀합니다.
Mail::to($request->user())->locale('ko')->send(
new OrderShipped($order)
);사용자별 선호 로케일
애플리케이션이 사용자마다 선호 언어를 저장하는 경우, 모델에 HasLocalePreference 컨트랙트를 구현하면 메일 발송 시 Laravel이 해당 로케일을 자동으로 사용합니다.
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Model implements HasLocalePreference
{
/**
* 사용자의 선호 로케일을 반환합니다.
*/
public function preferredLocale(): string
{
return $this->locale;
}
}인터페이스를 구현하면, Mailable과 알림(Notification)을 해당 모델로 전송할 때 Laravel이 선호 로케일을 자동으로 적용합니다. 따라서 locale 메서드를 별도로 호출할 필요가 없습니다.
Mail::to($request->user())->send(new OrderShipped($order));NOTE
다국어 서비스를 운영 중이라면 HasLocalePreference를 User 모델에 한 번만 구현해 두면, 메일과 알림 전체에 일관되게 적용됩니다. 언어별 분기 로직을 발송 코드마다 반복할 필요가 없어집니다.
테스트
Mailable 내용 테스트
Laravel은 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']);
}assertSeeInHtml 계열 메서드는 메일의 HTML 버전에 특정 문자열이 포함되어 있는지를, assertSeeInText 계열 메서드는 텍스트 버전에 포함되어 있는지를 각각 검사합니다.
Mailable 발송 테스트
Mailable의 내용 테스트와 발송 여부 테스트는 분리해서 작성하는 것을 권장합니다. 발송 여부를 테스트할 때는 메일 내용 자체보다, Laravel이 특정 Mailable을 올바른 대상에게 발송하도록 지시받았는지에 집중하면 됩니다.
Mail 파사드의 fake 메서드를 호출하면 실제 메일 발송을 막을 수 있습니다. 이후 Mailable이 발송 지시를 받았는지, 어떤 데이터를 담고 있었는지 어서션으로 검증할 수 있습니다.
Pest
<?php
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
test('orders can be shipped', function () {
Mail::fake();
// 주문 배송 로직 실행...
// 아무 메일도 발송되지 않았는지 확인...
Mail::assertNothingSent();
// 특정 Mailable이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class);
// 특정 Mailable이 2회 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 2);
// 특정 이메일 주소로 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// 여러 이메일 주소로 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// 특정 Mailable이 발송되지 않았는지 확인...
Mail::assertNotSent(AnotherMailable::class);
// 특정 Mailable이 2회 발송되었는지 확인...
Mail::assertSentTimes(OrderShipped::class, 2);
// 총 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();
// 주문 배송 로직 실행...
// 아무 메일도 발송되지 않았는지 확인...
Mail::assertNothingSent();
// 특정 Mailable이 발송되었는지 확인...
Mail::assertSent(OrderShipped::class);
// 특정 Mailable이 2회 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 2);
// 특정 이메일 주소로 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, 'example@laravel.com');
// 여러 이메일 주소로 발송되었는지 확인...
Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);
// 특정 Mailable이 발송되지 않았는지 확인...
Mail::assertNotSent(AnotherMailable::class);
// 특정 Mailable이 2회 발송되었는지 확인...
Mail::assertSentTimes(OrderShipped::class, 2);
// 총 3개의 Mailable이 발송되었는지 확인...
Mail::assertSentCount(3);
}
}Mailable을 백그라운드 큐에 넣어 처리하는 경우에는 assertSent 대신 assertQueued 메서드를 사용해야 합니다.
Mail::assertQueued(OrderShipped::class);
Mail::assertNotQueued(OrderShipped::class);
Mail::assertNothingQueued();
Mail::assertQueuedCount(3);발송되었거나 큐에 등록된 Mailable의 총 개수를 확인하려면 assertOutgoingCount 메서드를 사용합니다.
Mail::assertOutgoingCount(3);assertSent, assertNotSent, assertQueued, assertNotQueued 메서드에 클로저를 전달하면, 특정 조건을 만족하는 Mailable이 발송되었는지 세밀하게 검증할 수 있습니다. 조건을 통과하는 Mailable이 하나 이상 존재하면 어서션이 성공합니다.
Mail::assertSent(function (OrderShipped $mail) use ($order) {
return $mail->order->id === $order->id;
});클로저 내부에서 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');
});첨부파일 검증도 마찬가지로 클로저 안에서 처리할 수 있습니다.
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 드라이버
log 메일 드라이버를 사용하면 메일을 실제로 발송하는 대신, 모든 메일 내용을 로그 파일에 기록합니다. 주로 로컬 개발 환경에서만 사용하는 방식입니다. 환경별 설정 방법은 설정 문서를 참고하세요.
HELO / Mailtrap / Mailpit
HELO나 Mailtrap 같은 서비스를 활용하면, smtp 드라이버를 그대로 사용하면서도 메일을 실제 수신자가 아닌 가상의 메일함으로 보낼 수 있습니다. 이 방식의 장점은 실제 이메일 클라이언트처럼 렌더링된 최종 메일을 눈으로 직접 확인할 수 있다는 점입니다.
Laravel Sail을 사용 중이라면 Mailpit으로 메일을 미리 볼 수 있습니다. Sail이 실행 중인 상태에서 http://localhost:8025에 접속하면 Mailpit 인터페이스를 바로 확인할 수 있습니다.
전역 수신 주소(to) 고정하기
Mail 파사드의 alwaysTo 메서드를 사용하면, 애플리케이션에서 발송되는 모든 메일의 수신 주소를 하나의 주소로 고정할 수 있습니다. 이 메서드는 보통 서비스 프로바이더의 boot 메서드에서 호출합니다.
use Illuminate\Support\Facades\Mail;
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
if ($this->app->environment('local')) {
Mail::alwaysTo('taylor@example.com');
}
}NOTE
alwaysTo 메서드를 사용하면, 메일에 설정된 cc나 bcc 주소는 모두 제거됩니다.
이벤트
Laravel은 메일을 전송하는 과정에서 두 가지 이벤트를 디스패치합니다. MessageSending 이벤트는 메시지가 전송되기 직전에, MessageSent 이벤트는 메시지가 전송된 직후에 디스패치됩니다.
NOTE
이 두 이벤트는 메일이 실제로 전송될 때 디스패치됩니다. 큐에 등록되는 시점이 아닙니다. 큐를 사용하는 경우, 워커가 Job을 처리하면서 실제로 메일을 발송할 때 이벤트가 발생합니다.
애플리케이션에서 이벤트 리스너를 등록하면 이 시점에 원하는 로직(로깅, 감사 기록 등)을 실행할 수 있습니다:
use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;
class LogMessage
{
/**
* 이벤트를 처리합니다.
*/
public function handle(MessageSending $event): void
{
// 예: 발송 직전에 메일 내용을 로그에 기록
}
}메일
커스텀 트랜스포트
Laravel은 다양한 메일 트랜스포트를 기본 제공하지만, 지원하지 않는 외부 서비스를 통해 이메일을 전송하고 싶다면 직접 트랜스포트를 작성할 수 있습니다. 커스텀 트랜스포트 클래스는 Symfony\Component\Mailer\Transport\AbstractTransport를 상속하고, doSend와 __toString 메서드를 구현해야 합니다.
아래는 Mailchimp Transactional API를 사용하는 커스텀 트랜스포트 예시입니다:
<?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 메서드를 통해 Laravel에 등록합니다. 등록은 보통 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로 해당 패키지를 설치한 후 Laravel에 등록하면 됩니다.
예를 들어, "Brevo"(구 Sendinblue) 트랜스포트를 추가하려면 먼저 아래 패키지를 설치합니다:
composer require symfony/brevo-mailer symfony/http-client설치가 완료되면, config/services.php에 Brevo API 키 설정을 추가합니다:
'brevo' => [
'key' => env('BREVO_API_KEY'),
],다음으로, 서비스 프로바이더의 boot 메서드에서 Mail 파사드의 extend 메서드를 사용해 트랜스포트를 등록합니다:
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',
// ...
],