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ức | Bắt buộc | Mô tả |
|---|---|---|
content() | Có | Trả về nội dung email |
envelope() | Không | Định nghĩa người gửi, người nhận, tiêu đề |
headers() | Không | Thêm custom headers |
attachments() | Không | Trả 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ểu | Bắt buộc | Mô tả |
|---|---|---|---|
address | string | Có | Địa chỉ email (ví dụ: user@example.com) |
name | string | Không | Tê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ểu | Mặc định | Mô tả |
|---|---|---|---|
from | Address\|string | '' | Địa chỉ người gửi |
to | array | [] | Danh sách người nhận chính |
cc | array | [] | Danh sách CC |
bcc | array | [] | Danh sách BCC (ẩn) |
replyTo | array | [] | Địa chỉ reply-to |
subject | string | '' | Tiêu đề email |
using | Closure\|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ức | Mô tả |
|---|---|
from(Address\|string $address, ?string $name = null): static | Đặt người gửi |
to(Address\|array\|string $address, ?string $name = null): static | Thêm người nhận |
cc(Address\|array\|string $address, ?string $name = null): static | Thêm CC |
bcc(Address\|array\|string $address, ?string $name = null): static | Thêm BCC |
replyTo(Address\|array\|string $address, ?string $name = null): static | Thêm reply-to |
subject(string $subject): static | Đặt tiêu đề |
using(Closure $callback): static | Thê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().
| Class | Constructor | Mô tả |
|---|---|---|
Text | string $content | Văn bản thuần túy |
Html | string $content | Nội dung HTML (kế thừa Text) |
Markdown | string $markdown | Markdown → HTML tự động (dùng league/commonmark) |
View | string $path, array $variables | Twig 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ểu | Mô tả |
|---|---|---|
$path | string | Đường dẫn tới file template Twig (tương đối với thư mục views) |
$variables | array | Mả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ức | Mô 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ểu | Mặc định | Mô tả |
|---|---|---|---|
messageId | ?string | null | Message-ID header, dùng để nhận diện email trong thread |
references | array | [] | Mảng Message-ID tham chiếu (References header) |
text | array | [] | 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):
| Event | Mô tả |
|---|---|
MessageSending | Kích hoạt trước khi email được gửi đi |
MessageSent | Kí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));