WordPress自定义文章首次发布触发邮件的钩子适配问题
问题根源
之前所有方案踩坑的核心原因是WordPress核心钩子的触发顺序问题:
publish_{post_type}、save_{post_type}这类钩子触发时,ACF自定义字段、媒体附件的元数据还没完成入库,所以首次发布时拿不到值,等后续更新操作时数据已经存完,才能正常读取wp_after_insert_post虽然在所有数据入库后才触发,但会在自动保存、草稿编辑、文章更新等所有写入操作时执行,没有做去重判断就会发大量冗余邮件- 用
sleep()做延迟是同步阻塞逻辑,会卡住当前PHP请求,直接影响后台操作和前台访问体验,不建议使用
可行实现方案
用「状态变更判断+异步定时任务+发信状态标记」的组合逻辑实现,可覆盖所有业务场景:
- 用
transition_post_status钩子监听文章状态变更,仅当文章首次从非发布状态切换为publish状态时,调度一个延迟1分钟执行的异步发信任务,既给所有数据入库留足缓冲时间,又不会阻塞当前请求 - 用文章自定义元数据永久标记发信状态,不管后续文章怎么更新、状态怎么调整,只要发过一次就不再重复触发
- 异步任务执行时二次校验文章状态和发信标记,避免异常情况下的重复发信,同时读取完整的文章内容、自定义字段、关联图片作为邮件内容和附件
完整实现代码如下,可直接放到主题functions.php或自定义功能插件中:
// 监听kat类型文章状态变更,首次发布时调度异步发信任务 add_action( 'transition_post_status', 'schedule_kat_adoption_notice', 10, 3 ); function schedule_kat_adoption_notice( $new_status, $old_status, $post ) { // 仅处理自定义文章类型kat if ( $post->post_type !== 'kat' ) { return; } // 排除自动保存、修订版本的触发 if ( wp_is_post_autosave( $post->ID ) || wp_is_post_revision( $post->ID ) ) { return; } // 仅在首次从非发布状态变为发布状态时执行 if ( $new_status !== 'publish' || $old_status === 'publish' ) { return; } // 已发过通知的文章不再重复调度 if ( get_post_meta( $post->ID, '_kat_adoption_notice_sent', true ) ) { return; } // 调度1分钟后执行的单发异步任务,避免重复调度 if ( ! wp_next_scheduled( 'send_kat_publish_notice', [ $post->ID ] ) ) { wp_schedule_single_event( time() + 60, 'send_kat_publish_notice', [ $post->ID ] ); } } // 异步任务回调:实际执行发信逻辑 add_action( 'send_kat_publish_notice', 'execute_kat_publish_email', 10, 1 ); function execute_kat_publish_email( $post_id ) { // 二次校验发信标记,避免重复发送 if ( get_post_meta( $post_id, '_kat_adoption_notice_sent', true ) ) { return; } // 校验文章状态,若发布后1分钟内被改回非发布状态则终止发信 $post = get_post( $post_id ); if ( ! $post || $post->post_status !== 'publish' ) { return; } // 读取文章基础数据 $post_title = get_the_title( $post_id ); $post_url = get_permalink( $post_id ); $post_content = wp_strip_all_tags( $post->post_content ); // 需保留HTML格式可移除该过滤 // 读取所有自定义字段 $colour = get_field( 'dier_kleur', $post_id ); $breed = get_field( 'dier_ras', $post_id ); $cast = get_field( 'dier_geneutraliseerd', $post_id ) == "1" ? "Ja" : "Nee"; $found = get_field( 'dier_gevonden', $post_id ) == "1" ? "Ja" : "Nee"; $cats = get_field( 'dier_bij_katten', $post_id ); $dogs = get_field( 'dier_bij_honden', $post_id ); $kkind = get_field( 'dier_kleine_kinderen', $post_id ); $gkind = get_field( 'dier_grote_kinderen', $post_id ); $flat = get_field( 'dier_flat', $post_id ); $garden = get_field( 'dier_tuin', $post_id ); // 收集需要作为附件的图片 $attachments = []; // 加入特色图 if ( has_post_thumbnail( $post_id ) ) { $thumb_path = get_attached_file( get_post_thumbnail_id( $post_id ) ); if ( $thumb_path && file_exists( $thumb_path ) ) { $attachments[] = $thumb_path; } } // 提取正文中插入的所有图片 if ( preg_match_all( '/wp-image-(\d+)/', $post->post_content, $img_matches ) ) { foreach ( array_unique( $img_matches[1] ) as $img_id ) { $img_path = get_attached_file( $img_id ); if ( $img_path && file_exists( $img_path ) ) { $attachments[] = $img_path; } } } // 组装邮件内容 $to = 'hardcoded-emailadres'; // 替换为实际收件邮箱 $subject = "待领养动物故事发布:" . $post_title; $message = "您好,\r\n\r\n"; $message .= "新的待领养动物故事已正式发布,以下为多平台同步内容:\r\n\r\n"; $message .= "=== 基础信息 ===\r\n"; $message .= "动物名称:" . $post_title . "\r\n"; $message .= "详情链接:" . $post_url . "\r\n\r\n"; $message .= "=== 动物属性 ===\r\n"; $message .= "毛色:" . $colour . "\r\n"; $message .= "品种:" . $breed . "\r\n"; $message .= "是否绝育:" . $cast . "\r\n"; $message .= "是否为救助流浪动物:" . $found . "\r\n"; $message .= "适配其他猫:" . $cats . "\r\n"; $message .= "适配其他狗:" . $dogs . "\r\n"; $message .= "适配低龄儿童:" . $kkind . "\r\n"; $message .= "适配高龄儿童:" . $gkind . "\r\n"; $message .= "适合公寓饲养:" . $flat . "\r\n"; $message .= "需要带花园住所:" . $garden . "\r\n\r\n"; $message .= "=== 故事正文 ===\r\n"; $message .= $post_content . "\r\n\r\n"; $message .= "注:所有相关图片已作为邮件附件添加,可直接取用同步到第三方平台。"; $headers = [ 'Content-Type: text/plain; charset=UTF-8' ]; // 发送邮件 $send_result = wp_mail( $to, $subject, $message, $headers, $attachments ); // 发信成功后标记已发送,永久避免重复发信 if ( $send_result ) { update_post_meta( $post_id, '_kat_adoption_notice_sent', 1 ); } }
补充说明
- 如果站点在
wp-config.php中开启了DISABLE_WP_CRON,需要配置服务器层面的定时任务访问wp-cron.php,否则异步任务不会执行 - 若需要发送HTML格式邮件,将邮件头的
Content-Type改为text/html,同时用wpautop()函数处理正文内容即可保留换行格式 - 狗对应的自定义文章类型如果需要复用逻辑,只需在
schedule_kat_adoption_notice函数的post_type判断中加入对应文章类型slug即可
内容的提问来源于stack exchange,提问作者solarpleure
相关产品推荐
相关产品推荐

