本文约 3,631 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:钱到账了,客户却在问”我下单成功了吗”
最常见的投诉不是物流慢,而是”下单之后什么也没收到”。客户看不到「订单已确认」,就会怀疑付款是否成功,接着去申请退款;客服则要人工翻后台核实,成本极高。另一类故障更隐蔽:管理员收到了「新订单」通知,但客户邮件石沉大海——这通常是 mail() 被主机禁用,或者 SaaS 邮件服务把站点域名判为未验证来源,静默丢弃。要把这件事解决干净,得先搞明白 WooCommerce 邮件是怎么被触发的。
原理分析:邮件由「状态机」驱动,不是按钮
WooCommerce 的邮件是绑定订单状态的。当订单状态变化时,WC_Order::status_transition() 触发 woocommerce_order_status_changed 动作,WC_Emails 里的 send_transactional_email() 再去匹配对应的邮件类。也就是说,只要你调用 wc_get_order()->update_status( 'completed' ),邮件就应该发出去;但如果你用的是 $wpdb 直接改 post_status,邮件一条都不会发——这是很多”批量导出/批量发货”脚本失手的根本原因。
后台 WooCommerce → 设置 → 邮件 里的列表,对应一些真实存在的邮件类:WC_Email_New_Order(管理员)、WC_Email_Customer_Processing_Order、WC_Email_Customer_Completed_Order、WC_Email_Customer_On_Hold_Order、WC_Email_Customer_Refunded_Order、WC_Email_Cancelled_Order、WC_Email_Customer_Note。
| 订单状态 | 触发的邮件 | 适用节点 |
|---|---|---|
| Pending payment → Processing | 客户:订单已收到 + 管理员:新订单 | 支付成功即刻 |
| Processing → Completed | 客户:订单已完成 | 虚拟商品可自动完成 |
| Completed → Refunded | 客户:已退款 | 全额/部分退款均可触发 |
| 任意 → Cancelled | 客户:订单已取消 | 库存回滚后发送 |
模板文件位于 wp-content/plugins/woocommerce/templates/emails/,覆盖时复制到 你的主题/woocommerce/emails/ 目录下同名文件,无需改动插件本体。
保姆级实操
步骤 1:先解决能不能发出去。 装 WP Mail SMTP 插件,把发送方式改为 SMTP 或 API(SendLayer、SMTP.com、Brevo 都是该插件官方支持的渠道)。配置完成后在插件内自带的邮件测试工具里发一封,收不到就先别做模板。
步骤 2:核对发件人与站点域名一致。 很多 ESP 要求 From 的域名做过 SPF/DKIM/DMARC 授权,否则直接判为伪造发件人被抛弃。
步骤 3:关闭不需要的通知。 客户 Notes 邮件频繁打扰时,可在 Woocommerce → 设置 → 邮件里逐个停用,或用过滤器精确关闭:
// 只对"低库存"类通知做开关控制,新订单通知保留
add_filter( 'woocommerce_email_enabled_low_stock', '__return_false' );
// 让管理员的新订单通知额外抄送仓库邮箱
add_filter( 'woocommerce_email_recipient_new_order', function ( $recipient, $order ) {
if ( 'cod' === $order->get_payment_method() ) {
$recipient .= ',warehouse@example.com';
}
return $recipient;
}, 10, 2 );
步骤 4:改造已付款订单的邮件标题。 用过滤器改写主题行是安全的做法,不用碰模板:
add_filter( 'woocommerce_email_subject_customer_processing_order', function ( $subject, $order ) {
return sprintf( '【%s】订单 %s 已收到,我们正在备货', get_bloginfo( 'name' ), $order->get_order_number() );
}, 10, 2 );
步骤 5:在邮件里插入自定义字段。 联名学号、门店自提码这类信息,靠 woocommerce_email_order_meta 输出:
add_action( 'woocommerce_email_order_meta', function ( $order, $sent_to_admin, $plain_text ) {
$code = get_post_meta( $order->get_id(), '_pickup_code', true );
if ( $code ) {
echo '<p>自提码:<strong>' . esc_html( $code ) . '</strong></p>';
}
}, 10, 3 );
// 完成订单时附带电子发票 PDF
add_filter( 'woocommerce_email_attachments', function ( $attachments, $email_id, $order ) {
if ( in_array( $email_id, [ 'customer_completed_order' ], true ) ) {
$dir = WP_CONTENT_DIR . '/invoices';
$file = sprintf( '%s/invoice-%s.pdf', $dir, $order->get_order_number() );
if ( file_exists( $file ) ) {
$attachments[] = $file;
}
}
return $attachments;
}, 10, 3 );
步骤 6:给虚拟商品设置自动完成。 WooCommerce → 设置 → 商品 → 虚拟商品(Virtual)里把”已完成”的自动完成开关打开,订单付款后直接到 Completed,触发对应邮件。WooCommerce 虚拟商品自动发货搭建
常见踩坑
- 直接改
post_status或调用wp_update_post而不走WC_Order::update_status(),邮件完全不触发。 - 案例: 某站点把SMTP端口写成 465 却选了 “None” 加密,插件侧不报错,邮件却全部卡在 “queued”,直到客户投诉才发现近两周的订单邮件一封没发。
- 主题复制了旧版本
emails/模板,升级 WooCommerce 后customer-completed-order.php里缺失新的wc_get_template()参数,页面报 PHP Warning。 - 邮件测试的收件箱是 Gmail,误以为”没收到”——实际进了推广标签。
- 用了
woocommerce_email_recipient_new_order但没有第二个参数声明,匿名函数只接收一个实参,导致$order为 null,$order->get_payment_method()直接致命错误。 - 自托管主机对
wp_mail做了频率限制,促销类群发触发限制后连带交易邮件被拒。 - 管理员邮件收件人写成多个邮箱却用了中文顿号分隔,解析失败。WordPress SMTP 邮件发送失败排查
总结
判断邮件链路健康状况的标准很简单:能否发出去(SMTP/ESP)、是谁触发的(状态机)、内容里有没有必要的业务信息(钩子注入)、以及有没有兜底监控(日志)。行动清单:① 用 SMTP 或 API 通道替代 mail();② 任何改订单状态的操作都走 update_status();③ 自定义内容用钩子而非硬改模板;④ 每周跑一遍下单自测。邮件是最容易被忽视却最直接影响复购的一环。
