# WPForms Email Delivery Debugging

6-step chain for diagnosing WPForms email delivery failures. Entries exist in the database, notification settings look correct, but emails never arrive.

## 1. Check entries in the database

```sql
SELECT entry_id, form_id, status, type, date, LEFT(fields, 200)
FROM wp_wpforms_entries
WHERE form_id = <FORM_ID>
ORDER BY entry_id DESC LIMIT 10;
```

If entries exist, the form is submitting correctly. Move to notifications.

## 2. Inspect notification settings

The notification JSON is stored in `wp_posts.post_content` under `settings.notifications`:

```sql
SELECT ID, post_title, LEFT(post_content, 500)
FROM wp_posts WHERE ID = <FORM_ID>;
```

Check for:
- `"paypal_commerce": "1"` in the notification — this gates email until PayPal confirms payment. Remove it.
- `email` field — is the recipient correct? Does the address actually exist?
- `enable` is `"1"`

**PayPal gate fix:** The flag `"paypal_commerce": "1"` is embedded in the notification JSON inside `wp_posts.post_content`:
```sql
UPDATE wp_posts
SET post_content = REPLACE(post_content, '"paypal_commerce":"1"', '')
WHERE ID = <FORM_ID>;
```

## 3. Direct SMTP test

Test the actual SMTP credentials directly (bypasses WordPress):

```python
import smtplib
s = smtplib.SMTP("c1113726.sgvps.net", 2525, timeout=15)
s.starttls()
s.login("contact@apextrackexperience.com", "apex.track!!")
msg = "From: contact@...\r\nTo: contact@...\r\nSubject: Test\r\n\r\nBody"
s.sendmail("from@domain.com", ["to@domain.com"], msg)
s.quit()
```

If this succeeds, the credentials and relay work. If it fails, check:
- Relay hostname resolves (`dig +short c1113726.sgvps.net`)
- Port 2525 (netcup VPS blocks 25/465/587)
- Credentials

## 4. wp_mail test

Test via WordPress's mail function (runs through WP Mail SMTP plugin):

```bash
cd /path/to/wordpress
php -r '
require_once "wp-config.php";
require_once "wp-includes/pluggable.php";
$sent = wp_mail("test@example.com", "Subject", "Body", ["From: sender@domain.com"]);
echo "wp_mail returned: " . ($sent ? "TRUE" : "FALSE");
'
```

If `wp_mail()` returns `TRUE` but email never arrives, the issue is downstream (SPF, MX, or mailbox).

## 5. SPF check — most common silent-failure cause

The SMTP relay IP must be authorized in the domain's SPF record. If not, the receiving server (MXroute's spam filter, etc.) silently drops the email even though the relay reports "250 OK id=...".

### Check SPF

```bash
dig +short TXT domain.com | grep spf
```

### Common SPF patterns

**SiteGround-hosted sites** that use `c1113726.sgvps.net:2525` as SMTP relay:

Current: `v=spf1 +a +mx include:domain.com.spf.auto.dnssmarthost.net ~all`
Fixed: `v=spf1 +a +mx ip4:35.212.86.161 include:domain.com.spf.auto.dnssmarthost.net ~all`

Where `35.212.86.161` = `c1113726.sgvps.net`.

### Understanding the include chain pitfall

`include:domain.com.spf.auto.dnssmarthost.net` resolves through multiple levels:

1. `domain.com.spf.auto.dnssmarthost.net` → `_30d8870...spf.dnssmarthost.net` → `include:_spf.mailspamprotection.com`
2. `_spf.mailspamprotection.com` → authorizes only MXroute's own IP ranges (`185.56.84.0/24`, etc.)

This chain authorizes the **mailbox server** (MXroute), NOT the **outbound SMTP relay** (SiteGround's `c1113726.sgvps.net`). They are different IP ranges. Adding `ip4:35.212.86.161` fixes this gap.

### Checking DNS provider

```bash
dig +short NS domain.com
```

- `ns1/ns2.siteground.net` → edit at SiteGround Dashboard → Websites → domain → DNS Zone Editor
- Cloudflare IPs (188.114.x.x) → edit via Cloudflare API or dashboard

## 6. MX verification

```bash
dig +short MX domain.com
```

**PITFALL — MXroute in front of SiteGround email:** SiteGround email customers have MX from `antispam.mailspamprotection.com` (MXroute's spam filter) but the actual mailbox is on SiteGround's servers. Mail flow:

```
WordPress → c1113726.sgvps.net (relay) → SPF check → MXroute spam filter → SiteGround mailbox
```

If SPF fails, MXroute drops before SiteGround ever sees it.

## Prevention — add CC backup

Once the SPF is fixed, add yourself as a secondary notification recipient so future issues don't blindside you:

```sql
UPDATE wp_posts
SET post_content = REPLACE(
    post_content,
    '"email":"contact@domain.com"',
    '"email":"contact@domain.com, g@germainebrown.com"'
)
WHERE ID IN (<FORM_IDS>) AND post_content NOT LIKE '%g@germainebrown.com%';
```

## Quick Reference: dig commands

```bash
# SPF record
dig +short TXT <domain> | grep spf
# Full include chain
dig +short TXT <domain>.spf.auto.dnssmarthost.net
dig +short TXT _spf.mailspamprotection.com
# DKIM
dig +short TXT mail._domainkey.<domain>
# DMARC
dig +short TXT _dmarc.<domain>
# MX records
dig +short MX <domain>
# Nameservers (who hosts DNS)
dig +short NS <domain>
```
