PHP批量发送邮件时服务器离线的待发邮件存储方案
核心问题结论
逐封发送邮件过程中如果没有主动做持久化存储,服务器离线宕机时,未发出的邮件会直接丢失,不会被服务器自动留存。
以你举的10封邮件发了6封、服务器关机剩4封未发的场景为例:如果这4封邮件的完整数据只存在PHP运行内存、AJAX请求临时上下文、$_POST等超全局变量里,关机后内存数据会被完全清空,没有任何留存可能;如果这4封邮件已经被你提前写入了持久化存储介质,重启后可以直接读取续发。
注意:不要混淆业务服务器和SMTP服务器的存储边界。只有你已经完成SMTP握手、把邮件完整推送给SMTP服务端并收到250响应的邮件,才会进入SMTP服务端的发信队列,SMTP服务器宕机也会留存;还在你业务服务器内存里、没推送到SMTP服务端的邮件,SMTP服务端完全无感知,不可能帮你暂存。
待发邮件存储方案对比
JSON文件存储
- 适用场景:日发信量1000封以内的小型项目,实现成本极低
- 实现逻辑:批量发信任务触发时,第一时间把所有待发邮件的完整信息(收件人、主题、正文、附件路径等)写入服务器本地JSON文件,给每封邮件标记初始状态为
pending;每成功调用PEAR Mail的send()方法发完1封,就实时更新JSON文件里对应邮件的状态为sent,记录发送时间。服务器重启后扫描该JSON文件,把所有pending状态的邮件重新加入发信流程即可。 - 缺陷:并发发信时容易出现文件读写锁冲突,JSON文件体积增大后解析效率会明显下降,没有内置失败重试、进度统计能力,不适合中大型发信场景。
生产环境推荐方案
- 数据库持久化队列(通用首选)
基于项目已在用的MySQL/PostgreSQL等关系型数据库建mail_queue表,核心字段包含mail_id、recipient、subject、content、attachments、status(pending/sent/failed)、retry_count、create_time、send_time。批量任务提交时先把所有待发邮件全量写入表中,状态统一设为pending;后端发信脚本循环拉取status=pending且retry_count小于预设重试阈值的邮件发送,拿到PEAR Mail返回的发送成功结果后,再把对应记录状态更新为sent,发送失败则累加retry_count。服务器宕机重启后,脚本会自动拉取所有未发送的记录续发,既不会丢数据,也能避免重复发送。 - 消息队列(高并发场景首选)
如果日发信量过万,可选用开启持久化配置的Redis List、RabbitMQ作为发信队列,消费者配置手动ACK机制:只有邮件真正发送成功,才从队列中删除对应消息;如果发送过程中服务器宕机,未被ACK的消息会在服务重启后自动重新投递给消费者,可靠性和发信吞吐量都优于数据库轮询方案。
实现避坑点
- 禁止把待发邮件列表只存在前端JS变量、
$_SESSION、PHP进程内存这类易失性存储介质中,进程退出、服务器关机时这类数据会直接清空,无恢复可能。 - 必须严格遵循「发送成功后再改状态」的逻辑:只有拿到PEAR Mail
send()方法的明确成功返回、确认SMTP服务端已接收邮件,才能把对应邮件标记为已发送,避免出现标记已发但实际未发出的漏发问题。 - 不要依赖前端jQuery AJAX请求做任务状态留存,AJAX请求属于一次性HTTP请求,服务器中途宕机时请求会直接中断,前端留存的任务数据无法作为可靠的续发依据。
内容的提问来源于stack exchange,提问作者chris oojer
相关产品推荐
相关产品推荐

