PHP场景下如何获取退信邮件对应的原始发件MessageID
绝大多数邮件服务提供商生成未送达报告(NDR/DSN)时不会自动填充References或In-Reply-To头,所以你从overview字段中拿不到对应值是正常现象,可通过以下两种方案解决问题:
方案1:解析退信内容提取原始Message-ID
标准投递状态通知属于multipart类型邮件,其中会完整携带原始邮件的头部信息,你需要获取完整邮件内容而非仅overview字段来提取:
- 先在循环中判断是否为退信:可通过发件人是否为
MAILER-DAEMON、主题包含「未送达」「Delivery Status Notification」「Undelivered Mail Returned to Sender」等特征判断 - 使用
imap_fetchstructure获取邮件结构,定位到包含原始邮件头的部分(通常是message/rfc822-headers类型,或者文本部分中引用的原始头信息) - 从原始头中匹配
Message-ID字段即可拿到你之前自定义的ID
参考代码片段:
foreach ($result as $overview) { $isNdr = str_contains(strtolower($overview->from), 'mailer-daemon') || str_contains(strtolower($overview->subject), 'undeliver') || str_contains(strtolower($overview->subject), '退回'); if (!$isNdr) { // 原有正常回复的处理逻辑 continue; } $originalMsgId = ''; // 获取邮件完整结构 $structure = imap_fetchstructure($mbox, $overview->msgno); if (isset($structure->parts) && count($structure->parts)) { foreach ($structure->parts as $partNum => $part) { // 优先匹配单独存储的原始邮件头部部分 if ($part->type == 5 && $part->subtype == 'RFC822-HEADERS') { $rawHeaders = imap_fetchbody($mbox, $overview->msgno, $partNum + 1); if (preg_match('/Message-ID:\s*<(.+?)>/i', $rawHeaders, $matches)) { $originalMsgId = $matches[1]; break; } } // 无单独头部部分则从文本内容中匹配 if ($part->type == 0 && $part->subtype == 'PLAIN' && empty($originalMsgId)) { $body = imap_fetchbody($mbox, $overview->msgno, $partNum + 1); if (preg_match('/Message-ID:\s*<(.+?)>/i', $body, $matches)) { $originalMsgId = $matches[1]; break; } } } } // 拿到$originalMsgId后即可去数据库匹配对应邀请记录 }
方案2:通过可变退信地址匹配(稳定性更高)
你提到不能修改Reply-To地址,这个方案完全不涉及Reply-To的改动,只需要调整发件时的Return-Path(即退信接收地址):
发件时将Return-Path设置为bounce+{唯一标识}@你的域名.com,这里的唯一标识可以直接用你生成的MessageID做URL安全编码,也可以用数据库里对应邀请记录的主键ID。
退信时邮件会发送到这个带标识的地址,你只需要解析收件地址中+后面的部分,就能直接拿到对应记录的标识,无需解析复杂的NDR内容,兼容性远高于前者。
注意:需要你的邮件服务商支持「加号地址」(plus addressing)功能,目前绝大多数主流邮件服务都支持该特性。
内容的提问来源于stack exchange,提问作者lvor
相关产品推荐
相关产品推荐

