如何通过PHP从php://stdin重发邮件并替换主题为别名
先理清楚整个流程:Postfix 把目标邮件投递到你写的 PHP 脚本 → 脚本从 php://stdin 读取原始邮件内容 → 用 MailMimeParser 解析邮件头和正文 → 从 To 字段里提取别名 → 把邮件主题替换为该别名 → 用 PHPMailer 重新发送出去。下面拆解每个环节的关键细节:
一、读取并解析原始邮件
这一步是基础,你之前能把 stdin 内容写入文件,说明读取没问题,但解析环节容易踩坑。用 MailMimeParser 要注意必须一次性读全 stdin 的内容,不能分段读取:
use ZBateson\MailMimeParser\Parser; // 一次性读取stdin中的完整原始邮件 $rawEmail = file_get_contents('php://stdin'); $parser = new Parser(); $message = $parser->parse($rawEmail);
$message 对象会封装邮件的所有头信息、正文、附件,后续操作全依赖这个对象。
二、从 To 字段提取别名
To 字段的格式可能是「显示名」<alias@domain.com>或者直接alias@domain.com,不用自己写正则匹配,MailMimeParser 已经做好了解析:
// 获取所有收件人地址对象 $toAddresses = $message->getHeader('To')->getAddresses(); // 提取第一个收件人邮箱@前面的部分,也就是你要的别名 $alias = $toAddresses[0]->getLocalPart();
不管 To 字段有没有显示名,getLocalPart()都能精准提取邮箱的本地部分。如果有多个收件人,根据你的业务需求调整提取逻辑即可。
三、替换主题并重发邮件
用 PHPMailer 重发时,别用 SMTP 模式(容易因配置错误导致脚本无响应),直接用 Postfix 本地的 sendmail 模式更适配环境。另外要保留原始邮件的正文、附件,只替换主题:
use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; require 'vendor/autoload.php'; $mail = new PHPMailer(true); try { // 用本地sendmail投递,避免SMTP配置问题 $mail->isSendmail(); // 保留原始发件人信息 $fromAddr = $message->getHeader('From')->getAddresses()[0]; $mail->setFrom($fromAddr->getEmail(), $fromAddr->getName()); // 设置重发的目标邮箱 $mail->addAddress('your-target@domain.com'); // 将主题替换为提取到的别名 $mail->Subject = $alias; // 处理正文:区分HTML和纯文本格式 if ($message->getHtmlContent()) { $mail->isHTML(true); $mail->Body = $message->getHtmlContent(); $mail->AltBody = $message->getTextContent() ?: strip_tags($message->getHtmlContent()); } else { $mail->isHTML(false); $mail->Body = $message->getTextContent(); } // 处理所有附件 foreach ($message->getAllAttachmentParts() as $attachment) { $mail->addStringAttachment( $attachment->getContent(), $attachment->getFilename(), PHPMailer::ENCODING_BASE64, $attachment->getContentType() ); } $mail->send(); } catch (Exception $e) { // 将错误写入日志方便排查 error_log("邮件重发失败: " . $mail->ErrorInfo); }
之前脚本无反应大概率是 SMTP 配置错误,换成isSendmail()直接调用 Postfix 本地投递,基本不会有问题。
四、Postfix 配置核对要点
你已经配置了 master.cf、transport 等文件,再核对几个关键项:
- master.cf 中的脚本投递条目:
my-mail-script unix - n n - - pipe flags=F user=www-data argv=/usr/bin/php /绝对路径/你的脚本.php
user要设置为有脚本执行权限的用户(比如 www-data 或 PHP 运行用户)argv中的 PHP 路径要准确,可用which php查看真实路径
- transport 映射配置:
your-alias-domain.com my-mail-script:
配置完成后必须执行postmap /etc/postfix/transport生成哈希表,否则 Postfix 不会识别
3. main.cf 启用 transport 映射:
transport_maps = hash:/etc/postfix/transport
五、常见问题排查
- 脚本无反应,Postfix 日志报错:
- 查看
/var/log/mail.log,如果出现pipe command exited with status 127,要么是 PHP 路径错误,要么是脚本不存在/无执行权限 - 脚本开头必须添加
#!/usr/bin/env php,告诉系统用 PHP CLI 执行
- 查看
- 重发邮件无主题:
- 添加
error_log("提取到的别名: " . $alias)日志,确认别名是否为空 - 检查 PHPMailer 的
Subject属性是否被后续代码覆盖
- 添加
完整可运行脚本
整合上述代码,加上完整错误处理:
#!/usr/bin/env php <?php use ZBateson\MailMimeParser\Parser; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; require __DIR__ . '/vendor/autoload.php'; error_log("邮件处理脚本启动"); try { $rawEmail = file_get_contents('php://stdin'); if (empty($rawEmail)) { throw new Exception("未从stdin读取到邮件内容"); } $parser = new Parser(); $message = $parser->parse($rawEmail); // 提取别名 $toHeader = $message->getHeader('To'); if (!$toHeader) { throw new Exception("邮件无To字段"); } $toAddresses = $toHeader->getAddresses(); if (empty($toAddresses)) { throw new Exception("To字段无有效地址"); } $alias = $toAddresses[0]->getLocalPart(); if (empty($alias)) { throw new Exception("无法提取别名"); } error_log("提取到别名: {$alias}"); // 重发邮件 $mail = new PHPMailer(true); $mail->isSendmail(); // 设置发件人 $fromAddresses = $message->getHeader('From')->getAddresses(); if (!empty($fromAddresses)) { $fromAddr = $fromAddresses[0]; $mail->setFrom($fromAddr->getEmail(), $fromAddr->getName()); } else { $mail->setFrom('noreply@yourdomain.com', '系统自动转发'); } // 设置目标收件人 $mail->addAddress('your-target@domain.com'); // 替换主题 $mail->Subject = $alias; // 处理正文 $htmlContent = $message->getHtmlContent(); $textContent = $message->getTextContent(); if ($htmlContent) { $mail->isHTML(true); $mail->Body = $htmlContent; $mail->AltBody = $textContent ?: strip_tags($htmlContent); } else { $mail->isHTML(false); $mail->Body = $textContent; } // 处理附件 foreach ($message->getAllAttachmentParts() as $attachment) { $mail->addStringAttachment( $attachment->getContent(), $attachment->getFilename(), PHPMailer::ENCODING_BASE64, $attachment->getContentType() ); } $mail->send(); error_log("邮件重发成功,主题: {$alias}"); } catch (Exception $e) { error_log("邮件处理失败: " . $e->getMessage()); exit(1); // 返回非0状态码,让Postfix重试 } exit(0);
内容的提问来源于stack exchange,提问作者konkaGhe

