Laravel 13 邮件系统详解

邮件是应用程序与用户沟通的重要渠道。Laravel 13 提供了优雅的邮件发送系统,支持多种邮件驱动和模板引擎。

配置

邮件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// config/mail.php
return [
'default' => env('MAIL_MAILER', 'smtp'),

'mailers' => [
'smtp' => [
'transport' => 'smtp',
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env('MAIL_EHLO_DOMAIN'),
],

'ses' => [
'transport' => 'ses',
],

'postmark' => [
'transport' => 'postmark',
],

'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],

'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],

'array' => [
'transport' => 'array',
],
],

'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

创建邮件

生成邮件类

1
2
php artisan make:mail OrderShipped
php artisan make:mail OrderShipped --markdown=emails.orders.shipped

邮件类结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;

class OrderShipped extends Mailable
{
use Queueable, SerializesModels;

public function __construct(
public Order $order
) {}

public function envelope(): Envelope
{
return new Envelope(
subject: 'Order Shipped',
from: 'orders@example.com',
replyTo: 'support@example.com',
tags: ['order'],
metadata: [
'order_id' => $this->order->id,
],
);
}

public function content(): Content
{
return new Content(
view: 'emails.orders.shipped',
with: [
'order' => $this->order,
'trackingUrl' => route('orders.track', $this->order->id),
],
);
}

public function attachments(): array
{
return [
// 附件
];
}
}

发送邮件

基本发送

1
2
3
4
5
6
7
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

Mail::to($user->email)
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));

链式发送

1
2
3
4
5
6
7
8
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));

Mail::to($user)
->locale('zh')
->send(new OrderShipped($order));

发送到特定邮件程序

1
2
3
Mail::mailer('postmark')
->to($user->email)
->send(new OrderShipped($order));

邮件内容

Blade 视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- resources/views/emails/orders/shipped.blade.php -->
<!DOCTYPE html>
<html>
<head>
<title>Order Shipped</title>
</head>
<body>
<h1>Your order has been shipped!</h1>

<p>Order #{{ $order->id }}</p>
<p>Tracking Number: {{ $order->tracking_number }}</p>

<a href="{{ $trackingUrl }}">Track your order</a>

<p>Thank you for your purchase!</p>
</body>
</html>

Markdown 邮件

1
2
3
4
5
6
7
8
9
public function content(): Content
{
return new Content(
markdown: 'emails.orders.shipped',
with: [
'order' => $this->order,
],
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- resources/views/emails/orders/shipped.blade.php -->
@component('mail::message')
# Order Shipped

Your order #{{ $order->id }} has been shipped!

@component('mail::button', ['url' => $trackingUrl])
Track Order
@endcomponent

Thanks,<br>
{{ config('app.name') }}
@endcomponent

Markdown 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@component('mail::message')
# Heading

The email body.

@component('mail::button', ['url' => $url, 'color' => 'blue'])
Button Text
@endcomponent

@component('mail::panel')
Panel content
@endcomponent

@component('mail::table')
| Laravel | Table | Example |
| ------- | ----- | ------- |
| Cell 1 | Cell 2| Cell 3 |
@endcomponent

Thanks,<br>
{{ config('app.name') }}
@endcomponent

附件

基本附件

1
2
3
4
5
6
7
8
9
10
11
12
use Illuminate\Mail\Mailables\Attachment;

public function attachments(): array
{
return [
Attachment::fromPath('/path/to/file'),

Attachment::fromPath('/path/to/file')
->as('receipt.pdf')
->withMime('application/pdf'),
];
}

从存储附件

1
2
3
4
5
6
7
8
9
public function attachments(): array
{
return [
Attachment::fromStorage('invoices/receipt.pdf'),

Attachment::fromStorageDisk('s3', 'path/to/file')
->as('document.pdf'),
];
}

从数据附件

1
2
3
4
5
6
7
public function attachments(): array
{
return [
Attachment::fromData(fn () => $this->pdfContent, 'report.pdf')
->withMime('application/pdf'),
];
}

可邮寄对象

使用可邮寄对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace App\Mail;

use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;

public function envelope(): Envelope
{
return new Envelope(
subject: 'Invoice Paid',
from: new Address('noreply@example.com', 'App Name'),
replyTo: [
new Address('support@example.com', 'Support'),
],
tags: ['invoice'],
metadata: [
'invoice_id' => $this->invoice->id,
],
);
}

队列邮件

队列发送

1
2
3
4
5
6
7
use App\Mail\OrderShipped;

Mail::to($user->email)
->queue(new OrderShipped($order));

Mail::to($user->email)
->later(now()->addMinutes(5), new OrderShipped($order));

邮件类队列

1
2
3
4
5
6
7
8
9
10
class OrderShipped extends Mailable
{
use Queueable;

public function __construct(
public Order $order
) {
$this->onQueue('emails');
}
}

本地化邮件

1
2
3
Mail::to($user->email)
->locale('zh')
->send(new OrderShipped($order));

测试邮件

邮件伪造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderShipped;

public function test_order_shipped_email(): void
{
Mail::fake();

$order = Order::factory()->create();

$this->postJson("/api/orders/{$order->id}/ship");

Mail::assertSent(OrderShipped::class);
Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
return $mail->order->id === $order->id;
});
Mail::assertSentTo([$user], OrderShipped::class);
Mail::assertNotSent(WelcomeEmail::class);
Mail::assertNothingSent();
Mail::assertQueued(OrderShipped::class);
Mail::assertNothingQueued();
}

预览邮件

1
2
3
4
Route::get('/mailable', function () {
$order = Order::find(1);
return new App\Mail\OrderShipped($order);
});

自定义邮件模板

发布邮件模板

1
php artisan vendor:publish --tag=laravel-mail

自定义 CSS

1
/* resources/views/vendor/mail/html/themes/default.css */

事件

邮件事件

1
2
3
4
5
6
7
8
9
use Illuminate\Support\Facades\Event;

Event::listen(function (\Illuminate\Mail\Events\MessageSending $event) {
// 邮件发送前
});

Event::listen(function (\Illuminate\Mail\Events\MessageSent $event) {
// 邮件发送后
});

最佳实践

1. 使用队列

1
2
3
4
5
// 好的做法
Mail::to($user)->queue(new OrderShipped($order));

// 不好的做法
Mail::to($user)->send(new OrderShipped($order));

2. 使用 Markdown 邮件

1
2
3
4
5
6
7
8
9
10
11
// 好的做法:使用 Markdown 组件
@component('mail::message')
# Order Shipped
@component('mail::button', ['url' => $url])
Track Order
@endcomponent
@endcomponent

// 不好的做法:手写 HTML
<h1>Order Shipped</h1>
<a href="{{ $url }}">Track Order</a>

3. 本地化邮件

1
2
3
Mail::to($user)
->locale($user->preferred_locale)
->send(new OrderShipped($order));

总结

Laravel 13 的邮件系统提供了强大而灵活的邮件发送能力。通过合理使用 Mailable 类、Markdown 模板、队列发送和附件功能,可以构建出专业的邮件系统。记住使用队列发送邮件以避免阻塞请求、使用 Markdown 组件保持邮件样式一致、并在测试中使用邮件伪造来验证邮件发送。邮件是用户沟通的重要渠道,良好的邮件体验可以提升用户满意度。