- 后端
- Web框架
【免费下载链接】symfony
The Symfony PHP framework
本指南基于 Symfony 开源仓库中的 MailerSend Bridge 文档(README.md)及其实现源码,完整讲解如何通过 Symfony Mailer 接入 MailerSend 邮件服务,包括 API 与 SMTP 两种传输方式的 DSN 配置、邮件头特性(标签与打开/点击追踪)、远程模板发送,以及 Webhook 回执事件的签名校验与事件转换。读完本文,你将能够在一个 Symfony 项目中从零配置并发送邮件,并搭建基于 RemoteEvent 的投递状态追踪链路。
一、MailerSend Bridge 是什么
MailerSend Bridge 是 Symfony Mailer 的一个邮件桥接组件(bridge),作用是让开发者不必关心 MailerSend 平台的 HTTP 接口细节,只需配置一条 DSN,就能像使用内置 transport 一样发送邮件。仓库中该组件位于src/Symfony/Component/Mailer/Bridge/MailerSend/目录,其composer.json声明包名为symfony/mailer-send-mailer、类型为symfony-mailer-bridge,并要求php >= 8.4.1、symfony/mailer ^8.2(见 composer.json)。
从目录结构看,组件由三部分组成(参见 MailerSend 目录):
Transport/:三种传输通道——MailerSendApiTransport(HTTP API)、MailerSendSmtpTransport(SMTP)与MailerSendTransportFactory(DSN 解析工厂);Webhook/:MailerSendRequestParser,用于解析 MailerSend 推送的 webhook 请求并做签名校验;RemoteEvent/:MailerSendPayloadConverter,把 webhook 载荷转换成统一的 Mailer 远程事件对象。
根据 CHANGELOG.md 的版本演进记录:6.3 加入该桥接组件,7.1 增加了 RemoteEvent 与 Webhook 支持,8.2 为 API 传输通道增加了RemoteTemplateEmail与TagHeader支持。
二、安装与启用
在 Symfony 项目中通过 Composer 安装即可,由于该组件被symfony/flex的 mailer recipes 自动识别,通常无需额外注册 bundle:
composer require symfony/mailer-send-mailer安装完成后,组件的三个 DSN scheme(mailersend、mailersend+smtp、mailersend+api)会被 Symfony Mailer 的传输工厂自动识别。其识别逻辑可以在 MailerSendTransportFactory.php 中看到:
final class MailerSendTransportFactory extends AbstractTransportFactory { public function create(Dsn $dsn): TransportInterface { return match ($dsn->getScheme()) { 'mailersend+api' => (new MailerSendApiTransport($this->getUser($dsn), $this->client, $this->dispatcher, $this->logger)) ->setHost('default' === $dsn->getHost() ? null : $dsn->getHost()) ->setPort($dsn->getPort()), 'mailersend', 'mailersend+smtp' => new MailerSendSmtpTransport($this->getUser($dsn), $this->getPassword($dsn), $this->dispatcher, $this->logger), default => throw new UnsupportedSchemeException($dsn, 'mailersend', $this->getSupportedSchemes()), }; } protected function getSupportedSchemes(): array { return ['mailersend', 'mailersend+smtp', 'mailersend+api']; } }几个关键点:
mailersend与mailersend+smtp是等价的,都走 SMTP 通道;SMTP 需要同时提供用户名与密码;mailersend+api走 HTTP API,只取 DSN 中的用户名部分作为 API Key;- DSN 的 host 若为
default,则使用组件内置的默认主机(API 为api.mailersend.com,SMTP 为smtp.mailersend.net); - 其他 scheme(如
mailersend+foo)会抛出UnsupportedSchemeException,工厂测试 MailerSendTransportFactoryTest.php 验证了该行为。
三、DSN 配置:API 与 SMTP 双通道
原文档给出的配置示例非常直接,放在.env中即可(README.md):
# API MAILER_DSN=mailersend+api://$MAILERSEND_API_KEY@default # SMTP MAILER_DSN=mailersend+smtp://$MAILERSEND_SMTP_USERNAME:$MAILERSEND_SMTP_PASSWORD@default3.1 API 通道(推荐)
MAILERSEND_API_KEY=your_api_key_here MAILER_DSN=mailersend+api://$MAILERSEND_API_KEY@defaultmailersend+api通道默认向https://api.mailersend.com/v1/email发送POST请求,以Authorization: Bearer <API_KEY>头完成鉴权(见 MailerSendApiTransport.php)。若需要自定义主机或端口,可以在 DSN 中指定:
# 自定义 API 端点 MAILER_DSN=mailersend+api://$MAILERSEND_API_KEY@example.com:8443从源码getEndpoint()可以看到,默认主机为api.mailersend.com,只有 DSN host 不是default时才使用自定义主机,端口同理(MailerSendApiTransport.php)。
3.2 SMTP 通道
MAILERSEND_SMTP_USERNAME=your_smtp_username MAILERSEND_SMTP_PASSWORD=your_smtp_password MAILER_DSN=mailersend+smtp://$MAILERSEND_SMTP_USERNAME:$MAILERSEND_SMTP_PASSWORD@defaultMailerSendSmtpTransport内部固定连接smtp.mailersend.net:587并设置用户名密码(MailerSendSmtpTransport.php)。注意 DSN 中的用户名与密码不可省略——工厂测试incompleteDsnProvider明确覆盖了"只给用户名""只给密码"都会被判定为不完整 DSN 的场景(MailerSendTransportFactoryTest.php)。
3.3 如何选择
- 需要发送远程模板、追踪打开/点击、打标签等高级能力,或追求更低延迟,选 API 通道;
- 需要与既有 SMTP 基础设施、防火墙策略兼容,或希望邮件走标准 SMTP 协议,选 SMTP 通道;
- 两者都受同一套 Symfony Mailer 抽象接口驱动,业务代码中通过
MailerInterface发送邮件时无感知差异。
四、发送普通邮件:API 载荷生成原理
通过mailersend+api发送时,MailerSendApiTransport会把Email对象转换成 MailerSend API 期望的 JSON 载荷,转换逻辑在getPayload()中(MailerSendApiTransport.php)。对应关系如下:
| Email 属性 | API 载荷字段 |
|---|---|
发件人(from) | from.email/from.name |
收件人(to) | to[].email/to[].name |
主题(subject) | subject |
抄送(cc) | cc[].email/cc[].name |
密送(bcc) | bcc[].email/bcc[].name |
回复地址(reply_to) | reply_to.email/reply_to.name |
| 纯文本正文 | text |
| HTML 正文 | html |
| 附件 | attachments[].content(base64)/filename |
| 内联图片 | attachments[].disposition = "inline"/id(content-id) |
| 打开/点击追踪 | settings.track_opens/settings.track_clicks |
| 标签 | tags[](最多 5 个) |
日常用法与内置 transport 完全一致:
use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\Transport; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; $transport = Transport::fromDsn($_ENV['MAILER_DSN']); $mailer = new Mailer($transport); $email = (new Email()) ->from(new Address('sender@example.com', '销售团队')) ->to('customer@example.com') ->cc('cc@example.com') ->bcc('bcc@example.com') ->replyTo('support@example.com') ->subject('欢迎加入') ->text('欢迎加入我们的服务!') ->html('<html><body><p>欢迎加入我们的服务!</p></body></html>'); $mailer->send($email);测试 MailerSendApiTransportTest.php 验证了上述字段的映射:请求 URL 为https://api.mailersend.com/v1/email,from/to的 email 与 name 一一对应,subject、text、html均按原样提交。
4.1 成功与失败判定
- 发送成功:API 返回 HTTP 202,并从响应头
x-message-id中取出消息 ID 回写到SentMessage; - 响应体非 JSON、状态码非 202、或响应
warnings[0].type === 'ALL_SUPPRESSED'(收件人被全部抑制)时,抛出HttpTransportException,异常消息包含服务端返回的 message 与状态码。
以上逻辑与对应异常路径均可在源码与测试中印证:MailerSendApiTransport.php、MailerSendApiTransportTest.php。
五、高级能力:远程模板、标签与追踪
5.1 远程模板(RemoteTemplateEmail,8.2+)
MailerSend 允许把模板托管在服务端,发送时只传模板 ID 与个性化变量。Symfony 8.2 起为 API 通道提供了RemoteTemplateEmail支持:
use Symfony\Component\Mailer\RemoteTemplateEmail; $email = (new RemoteTemplateEmail()) ->template('tpl_123', ['firstName' => 'Fabien']); $mailer->send($email);载荷生成规则(MailerSendApiTransport.php):
- 载荷携带
template_id; - 若模板带有变量,则生成
personalization数组,为每个收件人生成{email, data}结构; - 未显式设置 subject、text、html 时,这些字段不会出现在载荷中,由服务端模板决定最终内容;
- 若同时设置了 subject,则
subject也会写入载荷(覆盖模板默认主题)。
对应测试 MailerSendApiTransportTest.php 分别验证了"带变量无主题"与"带主题无变量"两种场景下template_id、personalization、subject的出现与否。
5.2 标签(TagHeader)
标签用于在 MailerSend 控制台分类邮件、辅助统计。通过TagHeader添加:
use Symfony\Component\Mailer\Header\TagHeader; $email = (new Email()) ->subject('订单确认 #10086') ->to('customer@example.com') ->from('shop@example.com') ->text('您的订单已确认。'); $email->getHeaders()->add(new TagHeader('order')); $email->getHeaders()->add(new TagHeader('transactional'));- API 通道会把标签收集进
tags[]数组(MailerSendApiTransport.php); - SMTP 通道则把所有标签合并成单个
X-MailerSend-Tags头(逗号分隔),并从邮件头中移除原始的TagHeader,避免重复(MailerSendSmtpTransport.php); - 限制:单封邮件最多 5 个标签,超出会抛出
TransportException。两个 transport 都内置了这个数量校验,测试 MailerSendApiTransportTest.php 给出了 6 个标签抛错的用例。
5.3 打开与点击追踪(TrackingHeader)
通过TrackingHeader可在 API 载荷的settings中开启/关闭打开与点击追踪:
use Symfony\Component\Mailer\Header\TrackingHeader; $email = (new Email())->from('from@example.com')->to('to@example.com'); $email->getHeaders()->add(new TrackingHeader(opens: true, clicks: true));载荷中会生成settings.track_opens与settings.track_clicks(MailerSendApiTransport.php)。两个开关可独立控制:只设置clicks: false时,载荷只有track_clicks: false,不出现track_opens字段——这一细节在测试 MailerSendApiTransportTest.php 中有明确断言。
六、Webhook 回执:从推送验签到 RemoteEvent
MailerSend 会在邮件送达、打开、点击、退订、投诉等时刻向你的回调 URL 推送 webhook。Bridge 在 7.1 起通过 Symfony 的 Webhook 与 RemoteEvent 组件完整支持这一链路。
6.1 请求解析与签名校验
MailerSendRequestParser(MailerSendRequestParser.php)继承自AbstractRequestParser,其处理流程:
- 请求匹配:仅接受
POST且Content-Type为 JSON 的请求(MethodRequestMatcher('POST')+IsJsonRequestMatcher组合); - 载荷形态判断:同时支持 MailerSend v1 与 v2 两种 payload 结构——v1 依赖
data.email.message.id与data.email.recipient.email字段,v2 依赖data.message_id与data.recipient/data.email字段;若type缺失或两种结构都不满足,返回 406 拒绝; - 签名校验:读取请求头
Signature,用 HMAC-SHA256 对原始请求体做hash_hmac('sha256', $payload, $secret),再以hash_equals做恒定时间比较,不匹配即抛RejectWebhookException(406, 'Signature is wrong.'); - 测试事件处理:MailerSend 的
webhook.test测试请求使用固定密钥test_Am3L1GuOIc4blLUuHqAPxxwkZaJyEk8G(类常量TEST_SECRET),校验通过后返回 HTTP 202 而不产生真实事件。
6.2 事件类型与 Payload 转换
MailerSendPayloadConverter(MailerSendPayloadConverter.php)把 webhook 载荷转换为两类 RemoteEvent:
投递事件(MailerDeliveryEvent):
webhooktype | 事件名 |
|---|---|
activity.sent | RECEIVED |
activity.delivered | DELIVERED |
activity.soft_bounced/activity.hard_bounced | BOUNCE(并携带reason) |
互动事件(MailerEngagementEvent):
webhooktype | 事件名 |
|---|---|
activity.clicked/activity.clicked_unique | CLICK |
activity.opened/activity.opened_unique | OPEN |
activity.unsubscribed | UNSUBSCRIBE |
activity.spam_complaint | SPAM |
每个事件还会填充:
created_at:按Y-m-d\TH:i:s.uP格式解析为DateTimeImmutable,格式非法则抛ParseException;- 收件人邮箱(v1/v2 字段均可兼容);
- 标签(
tags); - 元数据:打开/点击事件含
ip(点击还含url),退订事件含reason/readable_reason; - 弹回事件的
reason会依次从morph.readable_reason、morph.reason、meta.bounce_reason中取值。
仓库为每个事件类型都准备了 v1/v2 两套 JSON 测试夹具(见 Tests/Webhook/v1/Fixtures 与 Tests/Webhook/v2/Fixtures),并有配套的请求解析器测试(含缺失签名、错误密钥等场景),可以直接参考它们理解各事件的实际载荷结构。
6.3 启用方式
Webhook/RemoteEvent 的能力依赖symfony/webhook与symfony/http-client(组件require-dev中声明为^7.4|^8.0,见 composer.json)。在启用了 Symfony Webhook 组件的应用中,把 MailerSend 回调地址指向你的 webhook 端点,并在端点配置中注册该 bridge 的解析器与密钥,即可把推送转换为标准的 Mailer RemoteEvent,再配合事件订阅器实现送达统计、退订告警等业务逻辑。
七、源码与测试参考
- 传输工厂与 DSN 支持范围:MailerSendTransportFactory.php
- API 传输实现(载荷生成、错误处理、远程模板):MailerSendApiTransport.php
- SMTP 传输实现(标签头合并):MailerSendSmtpTransport.php
- Webhook 解析与签名校验:MailerSendRequestParser.php
- 事件转换器:MailerSendPayloadConverter.php
- 传输测试:MailerSendApiTransportTest.php、MailerSendTransportFactoryTest.php
- 版本演进:CHANGELOG.md
- 组件依赖声明:composer.json
注意:API 通道的能力(远程模板、追踪设置、标签)以 8.2 及以上版本为准,Webhook/RemoteEvent 能力需要同时启用 Symfony Webhook 组件并配置回调密钥。动手实践时,建议先在 MailerSend 控制台创建发送域名与 API Key,再以.env中的 DSN 完成本地验证,最后用 webhook 测试请求确认签名链路是否打通。
- 后端
- Web框架
【免费下载链接】symfony
The Symfony PHP framework
相关推荐
Symfony Mailjet Mailer Bridge 使用指南:DSN 配置、API/SMTP 双通道与 Webhook 事件接入
Symfony Mailjet Mailer Bridge 使用指南:DSN 配置、API/SMTP 双通道与 Webhook 事件接入 本指南以 Symfon
后端Web框架Symfony Mailer 集成 Resend:从 SMTP/API 双通道配置到 Webhook 事件解析
Symfony Mailer 集成 Resend:从 SMTP/API 双通道配置到 Webhook 事件解析 本文以 Symfony 官方仓库中的 Resen
后端Web框架Symfony Brevo Mailer Bridge 完全指南:DSN 双通道配置、远程模板与 Webhook 事件解析
Symfony Brevo Mailer Bridge 完全指南:DSN 双通道配置、远程模板与 Webhook 事件解析 本文基于 Symfony 官方仓库中
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考