# WPForms Email Delivery Debugging ## Common Failure Modes on SiteGround/RunCloud WordPress Hosting ### 1. PHP Serialization Length Mismatch **Symptom:** WP Mail SMTP plugin shows "Authentication failed" or silently drops emails. Debug log shows SMTP connection succeeds but auth fails. **Root cause:** When the SMTP password is updated via the database (not the plugin UI), the PHP serialized string length in `wp_options` may not match the actual password length. Example: password `apex.track!!` (13 chars) stored as `s:72:"apex.track!!"` (declares 72 chars). PHP unserialization fails and the password field comes back empty. **Check:** ```sql SELECT LENGTH(JSON_UNQUOTE(JSON_EXTRACT(option_value, '$.smtp.pass'))) as pass_len FROM wp_options WHERE option_name = 'wp_mail_smtp'; ``` If NULL, the serialization is broken. **Fix:** ```sql UPDATE wp_options SET option_value = REPLACE(option_value, 's:72:\"apex.track!!\"', 's:13:\"apex.track!!\"') WHERE option_name = 'wp_mail_smtp'; ``` Replace `s:72` with `s:N` where N = actual character count of the password. ### 2. Invalid Sender Address (Multiple Emails) **Symptom:** SMTP server rejects with "X domain is not currently owned by sender" or similar. WPForms notifications specify a `sender_address` that may differ from the SMTP login. **Check:** ```sql SELECT JSON_EXTRACT(post_content, '$.settings.notifications."1".sender_address') FROM wp_posts WHERE ID = ; ``` **Fix:** Ensure `sender_address` is a single email matching the SMTP login user. If it contains comma-separated emails, remove extras. The notification's `email` field handles multiple recipients — `sender_address` is the `From:` header only. ```sql UPDATE wp_posts SET post_content = REPLACE(post_content, 'sender_address\":\"contact@site.com, admin@site.com', 'sender_address\":\"contact@site.com') WHERE ID IN (); ``` ### 3. Diagnostic Script ```bash # Check SMTP connectivity from WordPress host echo | openssl s_client -connect :2525 -starttls smtp 2>&1 | grep -c 'CONNECTED' # Test SMTP login python3 -c " import smtplib, ssl ctx = ssl.create_default_context() with smtplib.SMTP('', 2525, timeout=15) as s: s.starttls(context=ctx) s.login('', '') msg = 'From: \\nTo: \\nSubject: Test\\n\\nBody' s.sendmail('', [''], msg) print('OK') " # Check WP Mail SMTP debug events for recent failures mysql -u -p'' -e " SELECT COUNT(*) FROM wp_wpmailsmtp_debug_events WHERE event_type = 0 AND created_at >= NOW() - INTERVAL 10 MINUTE; " # Full debug log for recent errors mysql -u -p'' -e " SELECT id, content FROM wp_wpmailsmtp_debug_events WHERE event_type = 0 ORDER BY id DESC LIMIT 3\G " ``` ### 4. Watchdog Pattern (every 5 min) The `apex-mail-watchdog.sh` script runs as a no_agent cron on Core, SSHing into the WordPress host to: 1. Send a test email via Python SMTP (silent on success) 2. Check `wp_wpmailsmtp_debug_events` for recent failures (silent on success) 3. On failure: alerts the user via Hermes cron's delivery mechanism **Key detail for the watchdog:** on success, the script exits 0 with NO stdout. Only failures produce output. This keeps the user's chat clean.