如何在WordPress中用Cron和Action Scheduler异步执行wp_mail避免超时
WordPress发布自定义文章后异步发送订阅邮件的解决方案
原代码的核心问题
- 同步调用
wp_mail,订阅用户较多时会阻塞文章发布流程,极易触发服务器超时 - 邮件头手动拼接存在格式错误(转义引号、换行符混用),可能导致邮件发送失败或被标记为垃圾邮件
- 邮件内容格式不规范,链接未转为可点击的HTML元素,用户体验差
推荐解决方案:使用WP Cron异步执行
WP Cron是WordPress内置的后台任务调度系统,能把邮件发送任务放到后台执行,完全不影响文章发布流程。
修改后的完整代码
// 注册自定义Cron事件的回调钩子 add_action('send_calendrier_subscriber_emails', 'send_calendrier_emails_callback', 10, 1); // 监听文章状态切换,调度邮件发送任务 add_action('transition_post_status', 'schedule_mails_on_publish', 10, 3); function schedule_mails_on_publish($new_status, $old_status, $post) { // 仅在首次发布「gestion_calendrier」类型文章时触发 if ( 'publish' !== $new_status || 'publish' === $old_status || 'gestion_calendrier' !== get_post_type($post) ) { return; } // 避免重复调度,检查是否已有待执行的任务 if (!wp_next_scheduled('send_calendrier_subscriber_emails', array($post->ID))) { // 立即执行任务,也可以设置延迟(比如 time() + 60 表示1分钟后执行) wp_schedule_single_event(time(), 'send_calendrier_subscriber_emails', array($post->ID)); } } // 实际执行邮件发送的回调函数 function send_calendrier_emails_callback($post_id) { $post = get_post($post_id); // 容错:验证文章存在且类型正确 if (!$post || 'gestion_calendrier' !== get_post_type($post)) { return; } // 用数组形式构建邮件头,避免手动拼接的格式错误 $headers = array( 'From' => 'Your Site <example@gmail.com>', 'Reply-To' => 'example@gmail.com', 'Content-Type' => 'text/html; charset=UTF-8', 'MIME-Version' => '1.0', 'Content-Transfer-Encoding' => '8bit' ); // 获取所有订阅用户的邮箱 $subscribers = get_users(array('role' => 'subscriber')); $emails = array_column($subscribers, 'user_email'); // 简化数组提取 // 构建规范的HTML邮件内容,转义链接避免安全风险 $post_link = esc_url(get_permalink($post)); $body = sprintf( '<p>Hey, 有新的日程条目发布啦!</p> <p>点击查看:<a href="%s">%s</a></p>', $post_link, get_the_title($post) ); // 只有邮箱列表非空时才发送邮件 if (!empty($emails)) { wp_mail($emails, '新日程条目发布!', $body, $headers); } }
方案优势
- 异步无阻塞:邮件发送在后台执行,文章发布瞬间完成,不会超时
- 格式规范:用数组传递邮件头,避免手动拼接的转义错误
- 安全可靠:用
esc_url处理链接,防止XSS风险;添加容错检查,避免无效执行 - 原生支持:无需额外插件,依赖WordPress自带功能,兼容性好
替代方案:异步HTTP请求(适用于WP Cron失效场景)
如果服务器禁用了WP Cron,可以用PHP curl发起异步请求,调用后台AJAX端点发送邮件:
代码示例
// 注册后台AJAX端点,仅允许有权限的用户访问 add_action('wp_ajax_send_calendrier_mails', 'ajax_send_calendrier_mails'); function ajax_send_calendrier_mails() { if (!current_user_can('publish_posts')) { wp_die('无权限操作'); } $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; $post = get_post($post_id); if (!$post || 'gestion_calendrier' !== get_post_type($post)) { wp_die('无效文章'); } // 复用邮件发送逻辑 $headers = array( 'From' => 'Your Site <example@gmail.com>', 'Reply-To' => 'example@gmail.com', 'Content-Type' => 'text/html; charset=UTF-8' ); $subscribers = get_users(array('role' => 'subscriber')); $emails = array_column($subscribers, 'user_email'); $post_link = esc_url(get_permalink($post)); $body = sprintf( '<p>Hey, 有新的日程条目发布啦!</p> <p>点击查看:<a href="%s">%s</a></p>', $post_link, get_the_title($post) ); if (!empty($emails)) { wp_mail($emails, '新日程条目发布!', $body, $headers); } wp_die('发送成功'); } // 文章发布时触发异步请求 add_action('transition_post_status', 'trigger_async_mail_request', 10, 3); function trigger_async_mail_request($new_status, $old_status, $post) { if ( 'publish' !== $new_status || 'publish' === $old_status || 'gestion_calendrier' !== get_post_type($post) ) { return; } // 构建AJAX请求参数 $url = admin_url('admin-ajax.php'); $data = array( 'action' => 'send_calendrier_mails', 'post_id' => $post->ID, 'nonce' => wp_create_nonce('send_calendrier_mails_nonce') // 安全验证 ); // 用curl发起异步请求,设置超时1秒立即返回 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 1); curl_exec($ch); curl_close($ch); }
注意事项
- 若订阅用户数量极大,建议分批发送邮件(比如每次发50个),避免被邮件服务商判定为垃圾邮件
- 可以用「WP Crontrol」插件检查WP Cron的运行状态,确保任务能正常触发
- 测试阶段先用少量测试用户验证邮件发送效果,再推广到正式环境
内容的提问来源于stack exchange,提问作者deric tchuigang
相关产品推荐
相关产品推荐

