Contact Form 7 Not Sending Email

Contact Form 7 Not Sending Email

This is a Step by Step for Solving This Particular Issue with NameCheap

Change [account] and [IP]to your servers settings. This was for a Namecheap registered domain. You will need to adjust if using another registar.

All cmds where run as root, except for those noted as being run in the Terminal under the relevant cPanel account. This assumes your cPanel account has ‘shell’ or ‘Jailed Shell’ enabled

What the issue was

The domain [account] was registered/hosted in DNS at Namecheap, using these authoritative nameservers:

dns1.registrar-servers.com.
dns2.registrar-servers.com.

cPanel had generated the local DKIM key, but the public DKIM TXT record was missing or malformed in the real DNS zone. SPF was also missing. Gmail was rejecting messages with:

550-5.7.26 Your email has been blocked because the sender is unauthenticated.
Gmail requires all senders to authenticate with either SPF or DKIM.
DKIM = did not pass
SPF [server.[domain.com] with ip: [[server_IP]] = did not pass

The important concept is: DKIM signing/key generation is local to the server, but SPF/DKIM/DMARC TXT records must be published wherever DNS is actually hosted. In this case, that was Namecheap, not cPanel Zone Editor.

After the correct records were added, Gmail accepted the Contact Form 7 message with:
250 2.0.0 OK
The email then landed in Spam, meaning delivery/authentication was fixed, but Gmail reputation/filtering still placed it in Spam. Your later log showed the old Gmail rejections before the DNS fix and the successful accepted message afterward.

AI Advising Problem & Fix

Cloudflare changes how you fix the SPF/DKIM/DMARC side, but it doesn’t touch the envelope-sender problem — that one’s independent of where DNS lives, so Option A or B from before is still needed regardless. The split that matters here: signing is local, publishing isn’t.

DKIM’s private key is generated and lives on your server — Exim signs outgoing mydomain.com mail with it no matter where the domain’s DNS is hosted. What has to live in DNS is the public key, plus the SPF and DMARC TXT records, and cPanel can only auto-publish those into a zone it actually controls. Since every account on this box uses Cloudflare nameservers, cPanel isn’t authoritative for mydomain.com, so if your server doesn’t host the domain’s nameservers, cPanel will still generate the suggested SPF/DKIM values but you have to paste them into your actual DNS provider yourself — the one-click “Install”/”Repair” buttons in Email Deliverability are built for the case where cPanel’s own nameservers hold the zone, and don’t do anything useful here. Worth knowing that cPanel can sometimes show a “success” message on these buttons anyway, with an easy-to-miss warning underneath that the change didn’t actually land anywhere — so don’t trust the green tick on its own.

What to actually do

  1. Get the exact values from cPanel (skip Install/Repair): cPanel > Email > Email Deliverability > Manage next to mydomain.com. If no DKIM key exists yet, there’s a one-time local generation step first — that part works fine without DNS authority. Once it exists, Manage shows the exact Name and Value for the SPF TXT record and the DKIM TXT record (typically something like default._domainkey.mydomain.com), plus DMARC if it’s suggesting one.
  2. Paste them into Cloudflare, not cPanel’s Zone Editor:
    Cloudflare dashboard > mydomain.com > DNS > Records > Add record > Type TXT.

Common trip-up: if cPanel’s suggested Name is fully-qualified (default._domainkey.mydomain.com), Cloudflare usually just wants default._domainkey in the Name field since it appends .mydomain.com itself — check the saved record so you don’t end up with the hostname doubled.
No proxy toggle to worry about — Cloudflare only proxies A/AAAA/CNAME, so TXT records are DNS-only by default.

  1. Confirm the SPF value actually authorizes xxx.xxx.xxx.xxx — that’s the server IP from your bounce log. cPanel’s suggested SPF normally includes it automatically, but it’s worth eyeballing since that’s the exact thing Gmail flagged.
  2. Wait a few minutes, refresh Email Deliverability. At that point cPanel is just doing a live public DNS lookup — it doesn’t care who owns the zone, so once Cloudflare answers correctly it should flip to Valid.
  3. While you’re in there — PTR. Unlike SPF/DKIM/DMARC, that one isn’t a Cloudflare setting at all, since it’s tied to the IP rather than the domain — it lives wherever xxx.xxx.xxx.xxx is allocated (your network/server provider, or WHM’s own reverse zone if you manage that block yourself). Today’s rejection was purely SPF/DKIM, so PTR is probably fine — just worth a glance since it’s on the same page.

Then re-test
Both fixes are needed together — correct SPF/DKIM with the wrong envelope sender still bounces, and a fixed envelope sender with no SPF/DKIM still bounces, since Gmail is checking that the authenticated sending identity actually matches a domain with valid records. Run the same test send, then check exim_mainlog for => with a 250 from Gmail instead of **.
Since every account on this server follows the same Cloudflare pattern, this manual copy-paste is something you’ll repeat per domain — worth knowing that this same deliverability check is exposed through cPanel’s API too, if you ever want to pull status for every account at once rather than clicking through Email Deliverability one by one.


Step-by-step fix outline
Step 1 — Set variables

Run as root or from server terminal:

DOMAIN="[account]"
CPUSER="[user]"
SERVER_IP="[server_IP]"
DKIM_SELECTOR="default"

Step 2 — Check where DNS is hosted

dig +short NS "$DOMAIN"

Expected result for this case:
dns1.registrar-servers.com.
dns2.registrar-servers.com.

That means DNS is at Namecheap, so do not rely on cPanel Zone Editor or Email Deliverability “Repair” to publish DNS.

Go to: Namecheap > Domain List > [account] > Manage > Advanced DNS


Step 3 — Confirm local DKIM key exists

uapi --output=jsonpretty --user="$CPUSER" EmailAuth ensure_dkim_keys_exist domain="$DOMAIN"

Good result looked like:
“msg” : “key is acceptable”,
“status” :
1

That means the server-side/local DKIM key exists.


Step 4 — Check DKIM DNS status

uapi --output=jsonpretty --user="$CPUSER" EmailAuth validate_current_dkims domain="$DOMAIN"

The original bad result was:
“state” : “MISSING”,
“records” : []

That meant the DKIM DNS record was not published.
Then after adding it incorrectly, the bad result was:

“state” : “MALFORMED”
“reason” : “The record is not a valid list of DKIM tags and values.”

That happened because the whole instruction text was pasted into Namecheap’s value field, like:

Type: TXT Record Host: default._domainkey Value: v=DKIM1…

The value must only start with:
v=DKIM1; k=rsa; p=


Step 5 — Add/fix DKIM in Namecheap

In Namecheap Advanced DNS, add or edit:

Type: TXT Record
Host: default._domainkey
Value: v=DKIM1; k=rsa; p=PASTE_THE_FULL_KEY_FROM_CPANEL
TTL: Automatic

For your actual [account] DKIM value, it was:

v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxfVsS3lJYWBtHLENsy2Yq31V4DFXQXjx2wndd/9IkwW0xsV6qCB1eb/DwYftixvNbA4IISKKT1q36rLKV0ph9mXN/IWv3amHOvjQNsvvr2F03X5TAXRph4oeDQqireFwMzT/Wqv/yVh1DrdDbd7I3mjrg8PQxTvFlXoloBMjSaIZVssrUuj3X+g5Mg7PwTP6M3o+M0kN+H2FJYOs82yK0xlN9tDr1aSDfyV9YtSzbmwL6mydt+oCRgUVVxBy4P45z3PjPHLU3oNcSw4wRV10gpqfdl3gDKDEEo5/ukGY3n4NS2oXIhayVBqqDon52kIZOLwqMbyjnN1bUT8TM4X3HQIDAQAB;

Important:

Host: default._domainkey
Do not use:
default._domainkey.[account]
Namecheap appends the domain automatically.


Step 6 — Check DKIM publicly

dig +short TXT "default._domainkey.${DOMAIN}" | tr -d '"'

Good output starts with:

v=DKIM1; k=rsa; p=

Bad output contains things like:
Type: TXT Record Host:


Step 7 — Validate DKIM again in cPanel

uapi --output=jsonpretty --user="$CPUSER" EmailAuth validate_current_dkims domain="$DOMAIN"

You want it to no longer show:

MISSING
or:
MALFORMED


Step 8 — Check SPF

echo "Current SPF:"
dig +short TXT "$DOMAIN" | tr -d '"' | grep -i 'v=spf1' || echo "No SPF found"
echo
echo "Checking whether SPF includes $SERVER_IP:"
dig +short TXT "$DOMAIN" | tr -d '"' | grep -i 'v=spf1' | grep -q "ip4:$SERVER_IP" \
  && echo "YES - SPF includes ip4:$SERVER_IP" \
  || echo "NO - SPF does not directly include ip4:$SERVER_IP"

Original result was:

No SPF found
NO – SPF does not directly include ip4:[server_IP]


Step 9 — Add SPF in Namecheap

In Namecheap Advanced DNS:

Type: TXT Record
Host: @
Value: v=spf1 +a +mx +ip4:[server_IP] ~all
TTL: Automatic

Important: only have one SPF record for the root domain. If one already exists, edit it instead of adding a duplicate.


Step 10 — Validate SPF

uapi --output=jsonpretty --user="$CPUSER" EmailAuth validate_current_spfs domain="$DOMAIN"

And re-check publicly:

dig +short TXT "$DOMAIN" | tr -d '"' | grep -i 'v=spf1'

Step 11 — Check/add DMARC

Check:

dig +short TXT "_dmarc.${DOMAIN}" | tr -d '"'

If missing, add this in Namecheap:

Type: TXT Record
Host: _dmarc
Value: v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:postmaster@[account]
TTL: Automatic

Then re-check:

dig +short TXT "_dmarc.${DOMAIN}" | tr -d '"'

Step 12 — Check PTR / reverse DNS

PTR is not fixed in Namecheap unless Namecheap controls the server IP block. It belongs to the IP/server provider.

echo "PTR for $SERVER_IP:"
dig +short -x "$SERVER_IP"

echo
echo "Server hostname:"
hostname -f

echo
echo "Forward lookup for server hostname:"
dig +short "$(hostname -f)"

Ideally:

[server_IP] > server.domain.com
server.domain.com > [server_IP]

Validate with cPanel:

uapi --output=jsonpretty --user="$CPUSER" EmailAuth validate_current_ptrs domain="$DOMAIN"

Contact Form 7 checks

Step 13 — Confirm WordPress site and plugin

From the [user] cPanel terminal:

cd /home/[user]/public_html
/usr/local/bin/wp option get siteurl
/usr/local/bin/wp plugin list | grep -i "contact-form-7"

Your good result was:

http://[account]
contact-form-7  active  none  6.1.6  off


Step 14 — List Contact Form 7 forms

When logged in as the cPanel user [user], do not use sudo.

cd /home/[user]/public_html
/usr/local/bin/wp post list \
  --post_type=wpcf7_contact_form \
  --post_status=any \
  --fields=ID,post_title,post_status,post_type

Your form was:

ID: [ID]
Title: Contact form 1
Status: publish


Step 15 — Check Contact Form 7 mail settings

cd /home/[user]/public_html
/usr/local/bin/wp post meta get [ID] _mail

Cleaner output:

cd /home/[user]/public_html
/usr/local/bin/wp eval '
$id = [ID];
$mail = get_post_meta($id, "_mail", true);

echo "Recipient: " . ($mail["recipient"] ?? "") . PHP_EOL;
echo "Sender/From: " . ($mail["sender"] ?? "") . PHP_EOL;
echo "Subject: " . ($mail["subject"] ?? "") . PHP_EOL;
echo "Additional headers:" . PHP_EOL . ($mail["additional_headers"] ?? "") . PHP_EOL;

Your settings were correct:

Recipient: [email address]
Sender/From: [_site_title] <wordpress@[account]>
Subject: [_site_title] “[your-subject]”
Additional headers:
Reply-To: [your-email]

This is correct because the From address is on the authenticated domain:

wordpress@[account]

and the visitor’s email is only used as:

Reply-To: [your-email]

Do not put the visitor’s Gmail/Yahoo/Hotmail address in the From field.


Testing delivery

Step 16 — Watch Exim log while sending a form

As root:

tail -f /var/log/exim_mainlog | grep --line-buffered -iE "[account]|gmail|spf|dkim|dmarc|<=|=>|\*\*|rejected|fail|error"

Stop with: CTRL + C


Step 17 — Check latest Gmail sends

grep -iE "[account]|[email address]|gmail-smtp-in|250 2.0.0|550-5.7.26|\*\*" /var/log/exim_mainlog | tail -n 80

Failure looks like:

550-5.7.26 Your email has been blocked because the sender is unauthenticated

Success looks like:

=> [email address] … H=gmail-smtp-in.l.google.com … C=”250 2.0.0 OK … – gsmtp”
Completed

Your successful message was:

1wfaUi-00000001jUO-1Pwt => [email address] … C=”250 2.0.0 OK … – gsmtp”
1wfaUi-00000001jUO-1Pwt Completed


Final status

Original issue:
Gmail rejected [account] contact form email because SPF/DKIM were missing or invalid.

Root cause:
DNS was hosted at Namecheap, not cPanel. cPanel had the local DKIM key, but Namecheap DNS did not have the correct public DKIM/SPF records.

Fix:
Added/fixed DKIM TXT at default._domainkey.[account] in Namecheap.
Added SPF TXT for [account] authorizing [server_IP].
Added/checked DMARC.
Confirmed Contact Form 7 uses wordpress@[account] as From and Reply-To: [your-email].

Result:
Gmail no longer rejected the message.
Gmail accepted it with 250 2.0.0 OK.
Message landed in Spam, so remaining issue is Gmail spam placement/reputation, not server delivery failure.

For future domains on this server, the same pattern is:

1. Check authoritative nameservers.
2. If DNS is not hosted by cPanel, do not rely on cPanel Zone Editor.
3. Generate/check DKIM locally with cPanel.
4. Copy DKIM/SPF/DMARC TXT records into the real DNS provider.
5. Validate with uapi EmailAuth.
6. Send test.
7. Check /var/log/exim_mainlog for either Gmail 250 OK or 550 rejection.

Similar Posts