overtrue/laravel-mail-aliyun
Send mail through Aliyun DirectMail from any Laravel app. Add your Aliyun access keys and region to config/mail.php, then use Laravel's familiar Mail API, mailables, notifications, and queued jobs—no custom code required. Integrates cleanly with existing mail configuration.
This release (4.0.0) is a modernisation pass: the package now runs on PHP 8.3+/Laravel 13 and Symfony Mailer 7.4/8, and the transport actually delivers again on current Laravel. If you are starting fresh, the flow is short.
php -v must be 8.3+, and the app must be on Laravel 13. On anything older, stay on the 3.x line — 4.x will not install.composer require <vendor>/<package>:^4.0
php artisan vendor:publish --tag=<package>-config
config/mail.php and point env vars at it:
'mailers' => [
'directmail' => [
'transport' => 'directmail',
'key' => env('DIRECTMAIL_KEY'),
],
],
Then set MAIL_MAILER=directmail (or keep it as a secondary mailer and select it per-mailable).Mail::mailer('directmail')->to($user->email)->send(new WelcomeMail($user));
Where to look first: UPGRADING.md (explicitly called out in the release notes), then the config file for the credential/endpoint keys, then the transport class if you need to see what headers the package maps to provider tags.
Per-mailer selection instead of global default. Keeping the API transport as a named mailer (directmail) and picking it per-mailable gives you an easy escape hatch to SMTP for the message shapes 4.0 now rejects:
public function mailer(): string
{
return $this->hasAttachments() ? 'smtp' : 'directmail';
}
Tags go through withSymfonyMessage. The SwiftMailer-era withSwiftMessage hook is gone. Tag/header callbacks now receive a Symfony\Component\Mime\Email:
return (new OrderShipped($order))
->withSymfonyMessage(function (Email $message) {
$message->getHeaders()->addTextHeader('X-Tag', 'order-shipped');
});
Unicode tag values are preserved in 4.0, so localised tag names ("請求書", "café") are safe — verify the provider's own tag charset limits, not the package's.
Envelope recipients you can rely on. Supported to/cc envelope recipients are passed through, and text/HTML alternatives are preserved, so standard multi-part Mailables behave normally. Signed requests are handled inside the transport — do not re-implement signing or hand-roll the HTTP call.
Testing pattern. Mail::fake() still short-circuits everything, so unit tests are unaffected. For tests that exercise the real transport path, mock outbound HTTP:
Http::fake(['*' => Http::response(['id' => 'abc'], 200)]);
Mail::mailer('directmail')->to('[email protected]')->send(new WelcomeMail($user));
Http::assertSent(fn ($req) => str_contains($req->url(), '/messages'));
CI matrix. Mirror the release's own validation: PHP 8.3 lowest/highest, 8.4 and 8.5. Highest/lowest dependency resolution is where the Symfony Mailer 7.4→8 boundary tends to bite.
Four message shapes now fail loudly instead of silently degrading. BCC recipients, hidden envelope-only recipients, attachments, and raw/unstructured messages throw. This is deliberate — the old behaviour either dropped data or leaked recipients into visible headers. Two migration options: route those messages through an SMTP mailer, or restructure the Mailable so BCC targets become to/cc (rarely acceptable). Audit your codebase for ->bcc(, ->attach(, and ->raw( before upgrading.
The transport send contract changed. Any custom transport, decorator, or subclass overriding the send method or its protected extension points needs review — the package's own release notes flag this as a breaking contract change. Grep for classes extending or wrapping the package's transport and re-run their tests first.
Search and replace withSwiftMessage → withSymfonyMessage. The callback signature changes from Swift_Message to Symfony\Component\Mime\Email, so the bodies need rewriting too, not just the method name. Silent no-ops here are easy to ship: the mail still sends, the tags just never appear. Assert on tags in a test rather than eyeballing the provider dashboard.
"Validated" does not mean "delivered." The 16 tests / 49 assertions cover behaviour with outbound HTTP mocked — no live email was sent. Before relying on this in production, send one real message per shape you use (plain text only, HTML+text alternative, unicode tags) against a staging key.
Config caching. After changing mailer config, run php artisan config:clear. Stale cached config with an old transport name is the most common "it still uses the wrong driver" report.
Extension points are where you'll actually debug. When a message arrives with the wrong tags or a missing alternative, drop a temporary Log::info() in your withSymfonyMessage callback and inspect the resulting Email before it hits the transport — that isolates package behaviour from provider behaviour.
How can I help you explore Laravel packages today?