Skip to main content

Mail

Framework cung cấp một lớp gửi email mạnh mẽ, được xây dựng trên nền tảng Symfony Mailer. Hệ thống hỗ trợ nhiều driver (SMTP, Mailtrap, RoundRobin), gửi email bất đồng bộ, đính kèm tệp, và nội dung HTML, Markdown hoặc Twig template.

Cấu hình

Cấu hình mail được đặt trong file config/mail.php:

return [
'default' => env('MAIL_DRIVER', 'smtp'),

'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'no-reply@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example App'),
],

'mailers' => [
'smtp' => [
'scheme' => 'smtp',
'host' => env('MAIL_HOST', 'smtp.mailtrap.io'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
],

'mailtrap' => [
'scheme' => 'mailtrap+api',
'host' => env('MAILTRAP_API_TOKEN'),
],

'roundrobin' => ['smtp', 'mailtrap'],
],
];

Các biến môi trường tương ứng trong file .env:

MAIL_DRIVER=smtp
MAIL_FROM_ADDRESS=no-reply@example.com
MAIL_FROM_NAME="My App"

MAIL_HOST=smtp.gmail.com
MAIL_USERNAME=your@email.com
MAIL_PASSWORD=your-password

MAILTRAP_API_TOKEN=your-mailtrap-token

Tạo Mailable

Một Mailable là một class PHP đại diện cho một email. Class này kế thừa Vietiso\Core\Mail\Mailable và phải implement phương thức content().

use Vietiso\Core\Mail\Mailable;
use Vietiso\Core\Mail\Mailables\Envelope;
use Vietiso\Core\Mail\Mailables\Html;

class WelcomeMail extends Mailable
{
public function __construct(private string $userName) {}

public function envelope(): Envelope
{
return new Envelope(
subject: 'Chào mừng bạn đến với ứng dụng!',
to: ['user@example.com'],
);
}

public function content(): Html
{
return new Html("<h1>Xin chào, {$this->userName}!</h1><p>Cảm ơn bạn đã đăng ký.</p>");
}
}

Các phương thức của Mailable

Phương thứcBắt buộcMô tả
content()Trả về nội dung email
envelope()KhôngĐịnh nghĩa người gửi, người nhận, tiêu đề
headers()KhôngThêm custom headers
attachments()KhôngTrả về danh sách file đính kèm

Gửi Email

Sử dụng Facade Mail để gửi email:

use Vietiso\Core\Mail\Facade\Mail;

// Gửi ngay lập tức
Mail::send(new WelcomeMail('Nguyễn Văn A'));

// Gửi bất đồng bộ (deferred - không chặn response)
Mail::defer(new WelcomeMail('Nguyễn Văn A'));

Address — Địa chỉ email

Address đại diện cho một địa chỉ email kèm tên hiển thị.

Tham sốKiểuBắt buộcMô tả
addressstringĐịa chỉ email (ví dụ: user@example.com)
namestringKhôngTên hiển thị (ví dụ: Nguyễn Văn A), mặc định ''
use Vietiso\Core\Mail\Mailables\Address;

new Address('user@example.com'); // Chỉ có email
new Address('user@example.com', 'Nguyễn Văn A'); // Email kèm tên

Envelope — Thông tin email

Envelope chứa toàn bộ thông tin về người gửi, người nhận và tiêu đề email.

Constructor parameters

Tham sốKiểuMặc địnhMô tả
fromAddress\|string''Địa chỉ người gửi
toarray[]Danh sách người nhận chính
ccarray[]Danh sách CC
bccarray[]Danh sách BCC (ẩn)
replyToarray[]Địa chỉ reply-to
subjectstring''Tiêu đề email
usingClosure\|array[]Callback(s) tùy chỉnh Symfony Email object

Mảng to, cc, bcc, replyTo chấp nhận chuỗi email hoặc đối tượng Address, sẽ được tự động chuẩn hóa.

use Vietiso\Core\Mail\Mailables\Envelope;
use Vietiso\Core\Mail\Mailables\Address;

public function envelope(): Envelope
{
return new Envelope(
from: new Address('sender@example.com', 'Người gửi'),
to: ['recipient@example.com'],
cc: ['cc@example.com'],
bcc: ['bcc@example.com'],
replyTo: ['reply@example.com'],
subject: 'Tiêu đề email',
);
}

Fluent methods

Phương thứcMô tả
from(Address\|string $address, ?string $name = null): staticĐặt người gửi
to(Address\|array\|string $address, ?string $name = null): staticThêm người nhận
cc(Address\|array\|string $address, ?string $name = null): staticThêm CC
bcc(Address\|array\|string $address, ?string $name = null): staticThêm BCC
replyTo(Address\|array\|string $address, ?string $name = null): staticThêm reply-to
subject(string $subject): staticĐặt tiêu đề
using(Closure $callback): staticThêm callback tùy chỉnh
public function envelope(): Envelope
{
return (new Envelope)
->from('sender@example.com', 'Người gửi')
->to('user@example.com', 'Người nhận')
->cc('team@example.com')
->subject('Thông báo quan trọng');
}

Tùy chỉnh nâng cao với using

Dùng callback using để truy cập trực tiếp vào đối tượng Symfony\Component\Mime\Email. Có thể truyền một callback hoặc một mảng callbacks:

use Symfony\Component\Mime\Email;

public function envelope(): Envelope
{
return new Envelope(
subject: 'Email tùy chỉnh',
using: [
function (Email $email) {
$email->priority(Email::PRIORITY_HIGH);
},
function (Email $email) {
$email->getHeaders()->addTextHeader('X-Mailer', 'MyApp');
},
],
);
}

Kiểm tra thông tin Envelope

Envelope cung cấp các phương thức hữu ích để kiểm tra thông tin email — thường dùng trong unit test:

$envelope = new Envelope(
from: new Address('sender@example.com', 'Sender'),
to: ['user@example.com'],
subject: 'Hello',
);

$envelope->isFrom('sender@example.com'); // true
$envelope->isFrom('sender@example.com', 'Sender'); // true
$envelope->hasTo('user@example.com'); // true
$envelope->hasSubject('Hello'); // true
$envelope->hasCc('cc@example.com'); // false
$envelope->hasBcc('bcc@example.com'); // false
$envelope->hasReplyTo('reply@example.com'); // false

Content — Nội dung email

Tất cả content types đều implement ContentInterface và có thể dùng làm giá trị trả về của content().

ClassConstructorMô tả
Textstring $contentVăn bản thuần túy
Htmlstring $contentNội dung HTML (kế thừa Text)
Markdownstring $markdownMarkdown → HTML tự động (dùng league/commonmark)
Viewstring $path, array $variablesTwig template render với dữ liệu động

HTML

use Vietiso\Core\Mail\Mailables\Html;

public function content(): Html
{
return new Html('<h1>Tiêu đề</h1><p>Nội dung email dạng HTML.</p>');
}

Plain Text

use Vietiso\Core\Mail\Mailables\Text;

public function content(): Text
{
return new Text('Đây là email dạng văn bản thuần túy.');
}

Markdown

Nội dung Markdown sẽ được tự động chuyển đổi sang HTML bằng league/commonmark:

use Vietiso\Core\Mail\Mailables\Markdown;

public function content(): Markdown
{
return new Markdown("# Xin chào!\n\nĐây là email viết bằng **Markdown**.");
}

Twig Template (View)

Dùng Twig template để render email với dữ liệu động.

Tham sốKiểuMô tả
$pathstringĐường dẫn tới file template Twig (tương đối với thư mục views)
$variablesarrayMảng biến truyền vào template
use Vietiso\Core\Mail\Mailables\View;

public function content(): View
{
return new View('emails/welcome.html.twig', [
'user' => $this->user,
'link' => 'https://example.com/activate',
]);
}

Attachments — Đính kèm tệp

Phương thức attachments() trả về một mảng các đối tượng Attachment.

use Vietiso\Core\Mail\Mailables\Attachment;

public function attachments(): array
{
return [
// Đính kèm từ đường dẫn tuyệt đối
Attachment::fromPath('/var/app/files/report.pdf'),

// Đính kèm từ thư mục storage
Attachment::fromStorage('invoices/invoice-001.pdf'),

// Đính kèm từ dữ liệu thô
Attachment::fromData(fn () => file_get_contents('/tmp/data.csv'), 'data.csv')
->withMime('text/csv'),
];
}

Các phương thức của Attachment

Phương thứcMô tả
Attachment::fromPath(string $path)Đính kèm từ đường dẫn file tuyệt đối
Attachment::fromStorage(string $path)Đính kèm từ thư mục storage/
Attachment::fromData(\Closure $data, string $name)Đính kèm từ dữ liệu thô
->as(string $name)Đặt tên hiển thị cho file đính kèm
->withMime(string $mime)Chỉ định MIME type

Headers — Custom Headers

Thêm các header tùy chỉnh vào email (hữu ích cho threading email).

Constructor parameters

Tham sốKiểuMặc địnhMô tả
messageId?stringnullMessage-ID header, dùng để nhận diện email trong thread
referencesarray[]Mảng Message-ID tham chiếu (References header)
textarray[]Mảng key-value cho các text header tùy chỉnh (ví dụ: X-Custom-Header)
use Vietiso\Core\Mail\Mailables\Headers;

public function headers(): Headers
{
return new Headers(
messageId: 'ticket-123@example.com',
references: ['original-456@example.com'],
text: [
'X-Custom-Header' => 'custom-value',
],
);
}

Gửi bất đồng bộ (Deferred)

Phương thức defer() gửi email trong một coroutine riêng biệt, không chặn response trả về cho client. Rất phù hợp để gửi email thông báo trong các request HTTP.

Mail::defer(new WelcomeMail($user->name));

Chọn driver thủ công

Nếu muốn gửi email qua một driver cụ thể thay vì driver mặc định:

// Gửi qua SMTP
Mail::driver('smtp')->send(new WelcomeMail('Người dùng'));

// Gửi qua Mailtrap (thường dùng khi testing)
Mail::driver('mailtrap')->send(new WelcomeMail('Người dùng'));

// Gửi qua RoundRobin (tự động chuyển đổi giữa các driver)
Mail::driver('roundrobin')->send(new WelcomeMail('Người dùng'));

Driver RoundRobin

Driver roundrobin phân phối email luân phiên qua nhiều transport khác nhau, hữu ích để tăng khả năng gửi và giảm tải:

// config/mail.php
'roundrobin' => ['smtp', 'mailtrap'],

Truy cập Symfony Transport

Nếu cần truy cập trực tiếp vào transport của Symfony Mailer (để cấu hình nâng cao):

use Vietiso\Core\Mail\Facade\Mail;

$transport = Mail::getSymfonyTransport();

Gửi email không qua Mailable (Fluent API)

Ngoài cách dùng Mailable, bạn có thể gửi email trực tiếp qua Facade với cú pháp fluent:

use Vietiso\Core\Mail\Facade\Mail;

Mail::to('user@example.com')
->subject('Thông báo nhanh')
->html('<p>Nội dung email.</p>')
->send();

Events

Framework phát ra hai sự kiện trong quá trình gửi email (đang được phát triển):

EventMô tả
MessageSendingKích hoạt trước khi email được gửi đi
MessageSentKích hoạt sau khi email đã được gửi thành công

Ví dụ hoàn chỉnh

// app/Mail/OrderConfirmationMail.php

use Vietiso\Core\Mail\Mailable;
use Vietiso\Core\Mail\Mailables\Attachment;
use Vietiso\Core\Mail\Mailables\Envelope;
use Vietiso\Core\Mail\Mailables\Headers;
use Vietiso\Core\Mail\Mailables\View;

class OrderConfirmationMail extends Mailable
{
public function __construct(private Order $order) {}

public function envelope(): Envelope
{
return new Envelope(
to: [$this->order->customer_email],
subject: "Xác nhận đơn hàng #{$this->order->id}",
);
}

public function content(): View
{
return new View('emails/order-confirmation.html.twig', [
'order' => $this->order,
]);
}

public function attachments(): array
{
return [
Attachment::fromStorage("invoices/order-{$this->order->id}.pdf")
->as('hoa-don.pdf')
->withMime('application/pdf'),
];
}

public function headers(): Headers
{
return new Headers(
messageId: "order-{$this->order->id}@example.com",
);
}
}
// Trong controller hoặc service
use Vietiso\Core\Mail\Facade\Mail;

Mail::defer(new OrderConfirmationMail($order));