WordPress 聯絡表單收不到查詢、郵件進入垃圾郵件或遭機器人刷:按順序排查
WordPress 联系表单收不到询盘、进垃圾箱、被机器人刷:按这个顺序排查
WordPress 聯絡表單故障可分為提交、發信、送達和機器人過濾等層,應依次驗證,避免把不同問題混在一起排查。先確認提交有否成功並儲存查詢,再檢查 wp_mail()、SMTP 及 SPF、DKIM、DMARC 認證。防機器人可結合蜜罐、時間戳、限流和內容檢查,並以資料庫記錄作為查詢來源,避免郵件失敗造成查詢遺失。
2026-10-23 5
版权
版权声明:
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议》和 《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
现象看着都像「表单坏了」,其实是四层完全不同的故障。顺序搞反,就会一直在改模板,而问题在发信域。
先分类:你遇到的是哪一层
| 现象 | 卡住的层 | 第一个要看的指标 |
|---|---|---|
| 后台没有任何记录,访客说提交了 | 提交层(前端 / 路由 / nonce) | 浏览器 Network 面板有没有 200 |
| 数据库 / 日志里有记录,邮件没收着 | 发信层(wp_mail / SMTP) |
wp_mail_failed 的报错 |
| 邮箱收到了,但在垃圾箱或「促销」里 | 送达层(SPF / DKIM / DMARC / 内容) | 原始邮件头里的 Authentication-Results |
| 收件箱一堆带链接的英文询盘 | 过滤层(机器人) | 提交间隔、字段内容特征 |
下面按这个顺序走,每一步都能单独验证,不要跳步。
步骤 1:先确认表单到底有没有提交成功
最常见的坑:整个表单只有「发邮件」这一条路。 邮件失败,询盘就彻底消失了,你连「有没有人提交过」都不知道。
先改成「先落库,再发信」。存成自定义文章类型或者独立表都行,关键是后台能看到。
然后不开浏览器,直接用 curl 打你自己的接口,把提交层和邮件层拆开看:
curl -i -X POST 'https://example.com/wp-admin/admin-ajax.php' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'action=aum_inquiry' \
--data-urlencode 'nonce=换成真实nonce' \
--data-urlencode 'name=Curl Test' \
--data-urlencode 'email=test@example.com' \
--data-urlencode 'message=This is a test message sent by curl.' \
--data-urlencode "ts=$(date +%s)"
返回值里带上 stored 和 mailed 两个布尔值,一眼就知道断在哪一层。如果 curl 返回 403,问题在 nonce 或安全插件;返回 200 但数据库没记录,问题在落库逻辑。
同时看服务端日志:
tail -f /var/log/nginx/error.log
tail -f wp-content/debug.log
步骤 2:确认 wp_mail() 有没有真的发出去
wp_mail() 返回 false 只是结果,原因在 wp_mail_failed 里。扔一个 mu-plugin 进去记录:
<?php
// wp-content/mu-plugins/aum-mail-log.php
add_action('wp_mail_failed', function (WP_Error $e) {
$d = (array) $e->get_error_data();
error_log(sprintf(
'[mail-fail] %s | to=%s | subject=%s',
$e->get_error_message(),
implode(',', (array) ($d['to'] ?? [])),
(string) ($d['subject'] ?? '')
));
});
add_filter('wp_mail', function ($args) {
error_log('[mail-try] to=' . implode(',', (array) $args['to']) . ' subject=' . $args['subject']);
return $args;
});
常见报错与对策:
Could not instantiate mail function—— 主机禁了mail(),必须走 SMTP。SMTP connect() failed—— 端口被墙或凭据错。Message body empty—— 模板渲染出了空字符串,查一下变量。- 没有任何报错也没收到 —— 大概率发出去了,去下一步。
主机自带的 sendmail 只适合调试。 共享主机的出口 IP 经常和大量垃圾邮件共用一个段,进垃圾箱几乎是必然。生产环境用 SMTP 或发信 API,并把发信日志留在自己手里。
步骤 3:查发信域认证,这一层最容易被忽略
在收件邮箱里打开「显示原始邮件」,找这三行:
authentication-results: spf=pass; dkim=pass; dmarc=pass
三个里有一个 fail,进垃圾箱就不奇怪。对应的 DNS 记录:
example.com. TXT "v=spf1 a mx include:_spf.your-provider.example ~all"
mail._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=公钥内容"
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
几点硬要求:
- SPF 一个域只能有一条 TXT 记录,多条会直接 fail。所有发信服务合并到同一条里。
- DMARC 先从
p=none开始,收报告观察,别一上来就reject。 - 发信 IP 要有 PTR 反解,云主机默认经常没有。
From必须是你的域名,访客地址放Reply-To。 直接把From写成访客的邮箱(From: visitor@gmail.com)属于伪造发件人,SPF 一定对不上。
$headers = [
'From: Example Web <no-reply@example.com>', // 必须是本站域名
'Reply-To: ' . $name . ' <' . $email . '>', // 访客地址放这里
'Content-Type: text/plain; charset=UTF-8',
];
步骤 4:内容层别自己把自己送进垃圾箱
- 正文全是图片、只有一句「Please contact us」、或者只剩一个短链 —— 分数很低。
- 一封邮件里塞五六个外链、短链、
$!!!100% FREE这类词 —— 直接掉分。 - 主题行直接把用户输入拼进去,用户输入里带换行就是 header injection,整封信可能被判为恶意。
清洗函数,所有进 header 的字段都要过一遍:
function aum_clean_header($v): string {
return trim(str_replace(["\r", "\n", "%0a", "%0d", "%0A", "%0D"], '', (string) $v));
}
正文用 sanitize_textarea_field(),邮箱用 sanitize_email() 后再 is_email() 校验。别自己写正则过滤 HTML。
步骤 5:机器人,分层挡,不要只靠一个验证码
单靠验证码的问题是可访问性和误杀,而且现在不少自动化脚本能过简单图形码。实际有效的是下面几层叠加,任何一层命中就静默丢弃:
<?php
// wp-content/mu-plugins/aum-inquiry.php
add_action('wp_ajax_nopriv_aum_inquiry', 'aum_handle_inquiry');
add_action('wp_ajax_aum_inquiry', 'aum_handle_inquiry');
function aum_handle_inquiry(): void {
// 第 1 层:nonce
if (!check_ajax_referer('aum_inquiry', 'nonce', false)) {
wp_send_json_error(['code' => 'bad_nonce'], 403);
}
// 第 2 层:蜜罐。正常用户看不见这个字段
if (!empty($_POST['website'])) {
wp_send_json_success(['ok' => true]); // 静默成功,不给机器人重试信号
}
// 第 3 层:时间戳。渲染到提交少于 3 秒,或超过 2 小时
$ts = (int) ($_POST['ts'] ?? 0);
$age = time() - $ts;
if ($ts <= 0 || $age < 3 || $age > 7200) {
wp_send_json_error(['code' => 'bad_timing'], 400);
}
// 第 4 层:限流。同一 IP 10 分钟最多 3 次
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$key = 'aum_rl_' . md5($ip);
$hit = (int) get_transient($key);
if ($hit >= 3) {
wp_send_json_error(['code' => 'rate_limited'], 429);
}
set_transient($key, $hit + 1, 10 * MINUTE_IN_SECONDS);
// 第 5 层:字段与内容规则
$name = aum_clean_header($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$message = sanitize_textarea_field($_POST['message'] ?? '');
if ($name === '' || !is_email($email) || mb_strlen($message) < 10) {
wp_send_json_error(['code' => 'invalid'], 400);
}
if (preg_match_all('#https?://#i', $message) > 2) {
wp_send_json_error(['code' => 'too_many_links'], 400);
}
// 先落库,再发信
$post_id = wp_insert_post([
'post_type' => 'aum_inquiry',
'post_status' => 'private',
'post_title' => $name . ' <' . $email . '>',
'post_content'=> $message,
], true);
$sent = wp_mail(
'sales@example.com',
'New inquiry from ' . $name,
$message . "\n\nIP: " . $ip,
['From: Example Web <no-reply@example.com>', 'Reply-To: ' . $name . ' <' . $email . '>']
);
wp_send_json_success(['ok' => true, 'stored' => !is_wp_error($post_id), 'mailed' => (bool) $sent]);
}
前端配合最小的三行:
<input type="text" name="website" tabindex="-1" autocomplete="off"
style="position:absolute;left:-9999px" aria-hidden="true">
<input type="hidden" name="ts" value="<?php echo esc_attr(time()); ?>">
几个容易踩的点:
- 蜜罐命中要返回成功。返回 403 会让脚本换策略重试,静默丢弃更省事。
- 时间戳要能防篡改。如果被人直接构造请求,加一层签名:
wp_hash($ts . 'aum_inquiry'),服务端hash_equals()比对。 - 限流键不要只用 IP。公司出口、手机网络会出现多人同 IP,可以叠加 UA 或会话 ID 做二级键。
- 不要在页面上暴露「为什么被拦」。错误码只给个泛化文案。
如果刷单量已经影响到正常询盘,可以在服务器前面那层按路径做频率限制,把 /wp-admin/admin-ajax.php 的 POST 单独限速,比在 PHP 里挡更省资源。
步骤 6:兜底设计,让「丢询盘」这件事不可能发生
- 询盘先入库,后台有一个可搜索的列表;邮件只是通知,不是数据源。
wp_mail失败时给管理员发站内通知或者写一条高优先级日志,别静默失败。- 表单旁边保留第二个入口。对话式接询盘可以少一次「填完没回音」的流失——AumChat 把规则、AI 和真人在同一条对话里接力,公开演示在
chat.aumcreate.com/demo,不用注册就能看到店主那一侧的工作台;客户端在后台也会提醒你。装到 WordPress 上用的是 AumChat 安装器:https://aumcreate.com/plugins/aumchat?utm_source=aliyun&utm_medium=content&utm_campaign=aliyun-wp-form-spam-missing。
一页速查
| 症状 | 先做这个 | 不要做这个 |
|---|---|---|
| 提交后无任何记录 | 落库 + curl 打接口 | 换模板 |
| 有记录但没邮件 | 打开 wp_mail_failed 日志 |
反复点提交 |
| 邮件进垃圾箱 | 查 Authentication-Results 三行 |
改邮件正文措辞 |
From 用了访客邮箱 |
改成本站域名 + Reply-To |
继续用默认设置 |
| 收到大量带链接的英文 | 蜜罐 + 时间戳 + 限流 | 只加一个验证码 |
排查的顺序永远是:提交 → 发信 → 认证 → 内容 → 机器人。前一层没验证通过,就不要动后面一层,否则你会同时改五处,最后不知道是哪一处生效的。
如果站点现有表单结构比较乱,想一次性把落库、发信、认证、限流都配到位,我们的代部署服务 $399、5 个工作日:https://aumcreate.com/setup?utm_source=aliyun&utm_medium=content&utm_campaign=aliyun-wp-form-spam-missing。
來源:阿里雲開發者社區 AI · developer.aliyun.com