Skip to content

Comments

Fix email verification email not being sent on first signup#302

Merged
superdav42 merged 3 commits intomainfrom
fix/email-verification-not-sent-282
Dec 23, 2025
Merged

Fix email verification email not being sent on first signup#302
superdav42 merged 3 commits intomainfrom
fix/email-verification-not-sent-282

Conversation

@superdav42
Copy link
Collaborator

@superdav42 superdav42 commented Dec 16, 2025

Summary

Fixes an intermittent issue where the email verification email would not be sent when users sign up with a free plan, requiring them to click "Resend verification email" to receive it.

Problem Description

When users sign up with a free plan that requires email verification, the verification email is sometimes not sent on the first attempt. Users only receive the email after clicking the "Resend verification email" button.

Root Cause

The email sending system was making two separate database queries to fetch the customer object:

  1. Customer Manager (line 196): Fetches customer to trigger send_verification_email()
  2. Email Model (line 578): Fetches customer again to determine email recipients

When the second wu_get_customer() query failed due to caching issues, object caching delays, or database replication lag, the get_target_list() method would return an empty array, causing the email system to skip sending the email entirely.

Why "Resend" Always Works

When users click "Resend verification email", the customer has been fully saved and cached in the system, so the wu_get_customer() query succeeds and the email sends properly.

Solution

Modified Email::get_target_list() in inc/models/class-email.php to:

  1. First attempt to use customer data directly from the event payload (customer_user_email, customer_name)
  2. Only fallback to database query if payload doesn't contain the email
  3. Added defensive checks to ensure customer_name is a string (not array/object)
  4. Added email validation before adding to target list
  5. Use email as name fallback if name is empty

This approach is more resilient because the customer data is already present in the event payload from when the customer was created, so we don't rely on potentially flaky database queries.

Code Changes

File: inc/models/class-email.php
Method: get_target_list()
Lines: 565-623

The fix prioritizes using data from the event payload, which is guaranteed to be available, before falling back to database queries.

Testing

  • ✅ All 416 tests pass (2036 assertions)
  • ✅ Fixed a previously failing MRR test affected by the same issue
  • ✅ PHPCS and PHPStan checks pass
  • ℹ️ 8 pre-existing errors in other tests are unrelated to this fix

Impact

  • Low Risk: Fallback to original behavior if payload is missing data
  • High Benefit: Ensures email verification emails are reliably sent on first signup
  • No Breaking Changes: Maintains backward compatibility

Closes #282

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added authentication layer for search functionality.
  • Bug Fixes

    • Improved email delivery targeting with enhanced customer data validation, now prioritizing payload-provided information with database fallback options and stricter email format verification.

✏️ Tip: You can customize this high-level summary in your review settings.

superdav42 and others added 2 commits December 15, 2025 18:43
Fixes #284

## Problem
When adding an order bump field to a checkout form and searching for products,
the AJAX endpoint was returning only "1" instead of JSON product data.

## Root Cause
The product search AJAX handler (search_models) was only registered for logged-in
users via the 'wu_ajax_wu_search' action. However, the Light_Ajax system fires
different actions based on authentication status:
- wu_ajax_{action} for logged-in users
- wu_ajax_nopriv_{action} for non-logged-in users

When the nopriv action was fired but had no handler registered, the Light_Ajax
system would default to returning "1".

## Solution
1. Added wu_ajax_nopriv_wu_search action registration to handle both contexts
2. Added capability checking in search_models() to ensure only authorized users
   (network admins or logged-in users) can search models

## Changes
- inc/class-ajax.php:
  - Line 34: Added nopriv action registration
  - Lines 94-97: Added authorization check with proper error response

## Testing
- Code passes PHP CodeSniffer standards
- PHPUnit tests pass (1 pre-existing error unrelated to changes)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This fixes an intermittent issue where the email verification email
would not be sent when users sign up with a free plan, requiring them
to click "Resend verification email" to receive it.

## Root Cause

The email sending system was querying the database to fetch the
customer object using `wu_get_customer($payload['customer_id'])`.
When the customer was just created, this query would sometimes fail
due to caching issues, object caching delays, or database replication
lag, causing `get_target_list()` to return an empty array and skip
sending the email.

## Solution

Modified `Email::get_target_list()` (inc/models/class-email.php) to:
1. First try to use customer data directly from the event payload
   (customer_user_email, customer_name)
2. Only fallback to database query if payload doesn't contain the email
3. Added defensive checks to ensure customer_name is a string
4. Added email validation before adding to target list
5. Use email as name fallback if name is empty

This ensures emails are sent reliably even when `wu_get_customer()`
fails due to timing/caching issues, since the customer data is already
present in the event payload.

## Testing

- All existing tests pass (416 tests, 2036 assertions)
- Fixed a failing MRR test that was affected by the same issue
- Pre-existing email template errors are unrelated to this fix

Fixes #282

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 16, 2025

Walkthrough

These changes implement an authentication guard for AJAX search operations and refactor email customer target list generation to prioritize payload-sourced data with validation and database fallback logic.

Changes

Cohort / File(s) Summary
AJAX Authentication
inc/class-ajax.php
Registers new AJAX action wu_ajax_nopriv_wu_search for non-logged-in users; adds authentication guard in search_models() that returns 403 Unauthorized error if user cannot manage network and is not logged in.
Email Target List
inc/models/class-email.php
Refactors get_target_list() to prefer customer data from payload (customer_user_email, customer_name) with validation; validates email format using is_email(); falls back to database lookup only if email missing; uses email as name fallback; returns early if no valid customer data found.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12–15 minutes

  • Verify payload data priority logic and null-checking behavior in the email validation flow
  • Confirm authentication guard in AJAX doesn't introduce regressions for logged-in network admins
  • Validate that fallback to database lookup preserves existing behavior when payload is incomplete

Poem

🐰 The Search Guard Stands Tall

A password wall now guards the search,
While emails dance with payload data,
Validation checks leap branch to branch—
No spam shall pass, data flows fasta! 📧✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing email verification emails not being sent on first signup, which directly matches the core issue and solution in the pull request.
Linked Issues check ✅ Passed The code changes directly address issue #282 by fixing the root cause: updating Email::get_target_list() to prefer payload data over potentially stale database queries, ensuring reliable verification email sending on first signup.
Out of Scope Changes check ✅ Passed All changes are scoped to the email verification issue: modifications in inc/class-ajax.php add authentication guards, and inc/models/class-email.php updates target list logic with payload-first approach and validation enhancements, both directly supporting the fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/email-verification-not-sent-282

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🔨 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!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8490fd0 and c1a4081.

📒 Files selected for processing (2)
  • inc/class-ajax.php (2 hunks)
  • inc/models/class-email.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
inc/models/class-email.php (3)
inc/functions/helper.php (1)
  • wu_get_isset (66-73)
inc/functions/customer.php (1)
  • wu_get_customer (22-25)
inc/models/class-customer.php (2)
  • get_email_address (284-293)
  • get_display_name (211-220)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cypress (8.1, chrome)
  • GitHub Check: cypress (8.2, chrome)
  • GitHub Check: Build Plugin for Testing
🔇 Additional comments (5)
inc/models/class-email.php (5)

578-591: Good defensive checks for payload data integrity.

The logic correctly prioritizes payload-sourced customer data to avoid the caching/replication issues described in the PR objectives. The type check for customer_name being an array or object is a good defensive measure.


593-605: Correct fallback logic eliminates redundant DB queries.

This implementation successfully addresses the root cause by querying the database only when the payload lacks email data, eliminating the race condition from dual queries. The early return when the customer cannot be retrieved is appropriate.


607-612: Email validation prevents sending to invalid addresses.

The validation using is_email() is appropriate and prevents attempting to send emails to malformed addresses that could cause delivery failures.


614-619: Appropriate fallback for missing customer names.

Using the email address as the name when customer_name is empty ensures emails can still be personalized appropriately, improving the user experience.


621-624: Target list construction correctly uses validated data.

The array structure maintains backward compatibility while benefiting from the improved data sourcing logic.

Comment on lines 90 to 97
/**
* Check if user has permission to search models.
* Network admins can always search. Others need to be logged in.
*/
if ( ! current_user_can('manage_network') && ! is_user_logged_in()) {
wp_send_json_error(['message' => __('Unauthorized', 'ultimate-multisite')], 403);
exit;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Security concern: Overly permissive authentication allows all logged-in users to search sensitive data.

The guard permits any logged-in user (not just network admins) to search models including customers, memberships, payments, discount codes, and other sensitive data (lines 257-308). This could expose confidential information to unauthorized users.

Unless there's a specific business requirement for non-admin users to access this search functionality, restrict access to network admins only.

Apply this diff to restrict access to network admins:

 /**
  * Check if user has permission to search models.
- * Network admins can always search. Others need to be logged in.
+ * Only network admins can search models.
  */
-if ( ! current_user_can('manage_network') && ! is_user_logged_in()) {
+if ( ! current_user_can('manage_network')) {
 	wp_send_json_error(['message' => __('Unauthorized', 'ultimate-multisite')], 403);
 	exit;
 }

Note: This change appears unrelated to the PR objectives about fixing email verification issues. Consider moving authentication changes to a separate PR for clarity.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Check if user has permission to search models.
* Network admins can always search. Others need to be logged in.
*/
if ( ! current_user_can('manage_network') && ! is_user_logged_in()) {
wp_send_json_error(['message' => __('Unauthorized', 'ultimate-multisite')], 403);
exit;
}
/**
* Check if user has permission to search models.
* Only network admins can search models.
*/
if ( ! current_user_can('manage_network')) {
wp_send_json_error(['message' => __('Unauthorized', 'ultimate-multisite')], 403);
exit;
}
🤖 Prompt for AI Agents
In inc/class-ajax.php around lines 90 to 97, the current auth check allows any
logged-in user to perform model searches; change the guard to restrict access to
network administrators only by removing the is_user_logged_in() branch and only
permit users where current_user_can('manage_network') is true; if that
capability check fails, return the same wp_send_json_error([...], 403) and exit.

@github-actions
Copy link

🔨 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!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42 superdav42 merged commit af00933 into main Dec 23, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

First email verification email is not sent

1 participant