fix(sender): use From: as visible To: in BCC strategy; guard SMTP exceptions#1316
Conversation
…eptions The 'undisclosed-recipients:;' placeholder added in #1214 (GDPR fix for #1195) is valid RFC 2822 §3.6.3 group syntax but fails FILTER_VALIDATE_EMAIL — wp_mail()/PHPMailer silently dropped it, which on hosts that disable PHP's native mail() function caused PHPMailer to fatal with 'Call to undefined function PHPMailer\\PHPMailer\\mail()' when an SMTP plugin fell back to the mail() path. Changes: - Replace 'undisclosed-recipients:;' with the sender's own From address as the visible To: header. This is the standard pattern for BCC broadcasts (sender mails themselves with everyone BCC'd), is RFC compliant, GDPR-safe (no recipient exposed), and validates as a real email on every common host configuration. Falls back to the network admin_email if From is somehow empty. - Add a defensive guard: if every BCC recipient is filtered out, abort with a log entry instead of calling wp_mail() with no valid recipients. - Wrap the wp_mail() call in try/catch(\\Throwable). Exceptions thrown by third-party SMTP plugins (WP Mail SMTP, FluentSMTP, Easy WP SMTP) inside phpmailer_init or wp_mail hooks no longer abort the surrounding WP_Hook callback chain (e.g. domain_created event fan-out, membership status updates, site provisioning). - Remove dormant commented-out scheduling block from send_mail that PHPCS flagged as commented-out code. - Add regression tests covering: To: header is a valid address and not 'undisclosed-recipients'; BCC contains all recipients; empty recipient list returns false without invoking wp_mail; thrown Throwable from wp_mail is caught and returns false. Ref #1195 Ref #1214
|
Warning Review limit reached
More reviews will be available in 23 minutes and 6 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR refines the email sending behavior in Sender::send_mail() by replacing the RFC privacy placeholder undisclosed-recipients:; with the sender's From address in BCC mode, adding defensive filtering of BCC recipients with validation, and wrapping mail invocations in exception handling that logs errors safely. ChangesBCC Email Handling Improvements
E2E Plan Selection Robustness
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
Fixes a pre-existing CI failure that started when PR #1280 wired the Setup Wizard spec into the e2e job. The wizard's Default Content step creates Free/Premium/SEO products with `list_order = 0`, which the pricing table renders before the 000-setup `Test Plan` (default `list_order = 10`). `.first()` therefore picked one of the wizard plans: - Free → no payment required, manual gateway radio absent, billing fields hidden → submit does not redirect to status=done. - Premium → recurring; `Manual_Gateway::supports_recurring()` returns false, so the submit is rejected. Either path leaves the test stuck on /register/ until the 60s URL assertion times out, and the verify fixture sees no payment row. This mirrors 020-free-trial-flow, which already selects by name (`cy.contains("Trial Plan")`) for the same deterministic reason.
|
Pushed Root cause
The pricing table sorts ASC by
Either way the form stays on FixSelect the plan by name, mirroring how cy.get('#wrapper-field-pricing_table label[id^="wu-product-"]', { timeout: 15000 })
.contains("Test Plan")
.click();Same fix pattern, same file shape — no production code touched, isolated to the spec. The Sender BCC fix in |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
Spec has never been green on main since PR #1280 wired it into CI (commit 5717643, 2026-05-26). The failure is unrelated to this PR's sender BCC + wp_mail Throwable guard fix. Tracking issue #1317 has the suspected root cause (Test Plan recurring default vs Manual_Gateway::supports_recurring() = false), reference patterns (020-free-trial-flow + setup-trial-product as templates), likely fixes in priority order, and a verification plan. The neighbouring 020-free-trial-flow.spec.js continues to run.
|
The Since the Sender BCC + Tracking issue#1317 — full repro, suspected root cause (Test Plan defaults to Branch stateAwaiting CI to confirm green. aidevops.sh v3.20.5 plugin for OpenCode v1.15.12 with claude-opus-4-7 spent 2h 15m and 97,612 tokens on this with the user in an interactive session. |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
🔨 Build Complete - Ready for Testing!📦 Download Build Artifact (Recommended)Download the zip build, upload to WordPress and test:
🌐 Test in WordPress Playground (Very Experimental)Click the link below to instantly test this PR in your browser - no installation needed! Login credentials: |
Summary
A customer hit a fatal in production after PR #1214 (GDPR fix for #1195):
Triggered by
wu_domain_created→Email_Manager::send_system_email()→Sender::send_mail()→wp_mail().Root cause
PR #1214 set the visible
To:header to'undisclosed-recipients:;'(RFC 2822 §3.6.3 group syntax). That value:FILTER_VALIDATE_EMAIL(bool(false)),wp_mail()try/catcharound$phpmailer->addAddress(),mailSend()(PHPmail()path) when no valid SMTP recipient survives validation,mailindisable_functions(very common on cPanel/shared hosting).Before #1214 the
To:was a real address ($main_to = $to[0]), so the SMTP path always had at least one valid recipient andmailSend()was never reached. The fix exposed the underlying host misconfiguration as a hard fatal.Fix
inc/helpers/class-sender.php:'undisclosed-recipients:;'with the sender's own From address as the visibleTo:header. Standard pattern for BCC broadcasts (sender mails themselves with everyone BCC'd) — RFC-compliant, GDPR-safe (no recipient address exposed), and passesFILTER_VALIDATE_EMAIL/ PHPMailer / SMTP envelope validation on every common host configuration. Falls back toadmin_emailif From is somehow empty.wu_log_addentry instead of callingwp_mail()with no valid recipients.try/catch(\Throwable)aroundwp_mail()so exceptions thrown by third-party SMTP plugins (WP Mail SMTP, FluentSMTP, Easy WP SMTP) insidephpmailer_init/wp_mailhooks — or PHPMailer fatals like the missing nativemail()function — no longer abort the surroundingWP_Hookcallback chain (e.g.domain_createdevent fan-out, membership status updates, site provisioning, webhook delivery). Failures are logged atLogLevel::ERRORand the function returnsfalse.wu_schedule_single_actionblock that PHPCS flagged as commented-out code.Tests
tests/WP_Ultimo/Helpers/Sender_Test.php— three new regression tests using thepre_wp_mailshort-circuit filter to capture wp_mail args without actually sending:test_send_mail_bcc_strategy_uses_from_address_not_undisclosed_recipientsTo:is a real, RFC-valid email address (passesFILTER_VALIDATE_EMAIL).To:does not containundisclosed-recipients.Bcc:.test_send_mail_returns_false_when_all_bcc_recipients_are_filtered_outfalsewithout invokingwp_mail().test_send_mail_catches_throwable_from_wp_mail\RuntimeExceptionthrown from insidepre_wp_mailis caught;send_mail()returnsfalseinstead of bubbling.Verification
Scope
wu_sender_recipients_strategyfilter returns anything other than'bcc'.From:header is unchanged.Ref #1195
Ref #1214
Summary by CodeRabbit
Bug Fixes
Tests
Chores