# WPForms Email Delivery Verification

Pattern for checking whether WPForms confirmation emails were actually delivered to form submitters, by cross-referencing IMAP inbox, bounce messages, WordPress DB entries, and DNS records.

## Overview

WPForms sends two categories of email on form submission:

1. **Admin notification** → sent to site admin/owner (e.g. `contact@domain.com`). These appear in the admin IMAP inbox.
2. **Confirmation to registrant** → sent to the person who filled the form. These go to external email addresses and may bounce.

## Verification Flow

### Step 1: Identify form IDs

Query the WordPress DB to discover which forms are active and their IDs:

```sql
SELECT ID, post_title FROM wp_posts 
WHERE post_type='wpforms' AND post_status='publish'
ORDER BY ID;
```

Common conventions: form ID 268 = waiver, 270 = paid registration (varies per site).

### Step 2: Get form entries from the DB

```sql
SELECT entry_id, date, status, LEFT(fields,800) 
FROM wp_wpforms_entries WHERE form_id=270 
ORDER BY entry_id;
```

Extract registrant names and emails from the `fields` JSON column. The JSON structure uses numbered field IDs as keys; each has a `value` and an `email` field.

### Step 3: Check admin notifications in IMAP inbox

Connect to the site's IMAP inbox and search for WPForms submission notifications:

```python
conn.search(None, f'(SINCE "{yesterday}")')
# Look for messages FROM the site's email with subjects containing 
# registration, waiver, or the form title
```

Admin notifications confirm the form was submitted and the pipeline triggered.

### Step 4: Check for bounce messages

Search for mailer-daemon / postmaster bounces:

```python
conn.search(None, 'OR FROM "mailer-daemon" FROM "postmaster"')
```

For each bounce, extract the original recipient address from the body:

```
Delivery to the following recipient failed permanently: user@gmail.com
Technical details: 550-5.7.26 Sender unauthenticated (SPF/DKIM)
```

### Step 5: Cross-reference bounces with registrants

Match bounce recipient addresses against registrant emails from Step 2. These are the registrants whose confirmations didn't arrive.

### Step 6: Trace SPF/DKIM root cause

From the bounce message body, extract the sending IP reported by the receiving MTA:

```
SPF [domain.com] with ip: [X.X.X.X] = did not pass
```

Then check:

```bash
# Current SPF record
dig +short TXT domain.com | grep spf

# Current DKIM
dig +short TXT default._domainkey.domain.com

# Current DMARC
dig +short TXT _dmarc.domain.com
```

**Common failure:** WP Mail SMTP sends through a relay (e.g. SiteGround `c*.sgvps.net`) which resolves to an IP not in the SPF record. Check the SMTP relay IP:

```bash
dig +short c1113726.sgvps.net
```

Then verify that IP is in the SPF include chain. If not, add it.

### Step 7: Assess remaining registrants

If no bounce was received for a registrant, the confirmation likely reached them. Limitations:
- Some MTAs silently drop (no bounce) if the mailbox is full
- Some spam filters never bounce
- Only Gmail/Outlook typically send proper bounce notifications

## Pitfalls

- **Bounces from MXroute IPs** — If the WordPress site was migrated between hosting providers, old SPF records may still reference the previous provider's sending IPs. The bounce IP is the actual sending IP, not the SMTP relay hostname.
- **WP Mail SMTP logs disabled** — The `wp_mail_smtp` option has `logs.enabled => false` by default in Lite mode. There are no sent-mail logs to check directly.
- **Form entries include test/submitted-but-unpaid entries** — Early entries may be test submissions from the site owner with $1.00 amounts. Filter these out by checking the payment field (`amount_raw`).
- **Admin notification vs registrant confirmation** — Just because the admin got a notification doesn't mean the registrant did. WPForms sends them separately through the same SMTP pipeline.
- **Form field IDs vary** — The `fields` JSON uses numeric field IDs that differ per form configuration. Parse the JSON and check field names, not IDs.
