Contact Form 7 runs on millions of sites for a good reason: it's free, light, and gets out of your way. I shipped it on client sites for years. The problem isn't that CF7 is bad — it's that it answers exactly one question ("did the form submit?") and stays completely silent on the one that actually matters in production: did the notification arrive?
Here's the call every developer who maintains WP sites has taken at least once:
"I filled in your contact form last week and never heard back."
You check. The form is fine. JavaScript fires, the success message shows, no console errors. CF7 did its job — it handed the message to wp_mail() and forgot it ever existed. There's no record the submission happened, and no log of whether the email was delivered, bounced, or quietly dropped by the host's unauthenticated sendmail. The lead is just gone, and you have nothing to debug with.
The three gaps that bite in production
- No submissions database. CF7 sends an email and discards the data. If the email fails or lands in spam, the submission never existed. (Flamingo helps, but it's a bolt-on — separate screen, no filtering or export out of the box, not tied to your form config.)
- No delivery log. You can't tell whether mail was sent, rejected, or bounced. "I never got it" has no audit trail to check against.
-
No native block. CF7 is still a shortcode —
[contact-form-7 id="123"]. You can't drop it into a block template, control its layout with block spacing, or edit it inline in Gutenberg. You paste a shortcode and hope.
None of these are dealbreakers for a throwaway contact form. All three are dealbreakers when a missed submission is a missed sale.
Migrating without rebuilding by hand
The reason most people put off switching isn't the feature gap — it's the thought of rebuilding every form field by field. That's the part I wanted to skip.
The migration path I use reads CF7's stored form definitions directly and recreates them as native forms. What comes across automatically:
- All standard fields — text, email, URL, tel, number, textarea, select, checkbox, radio, file upload
- Visible field labels
- Placeholder text
- Required-field rules
What doesn't (and why it's fine): custom PHP validation hooks that lived in your theme, Flamingo's historical submissions, and CF7's mail-tab config. The importer handles form structure, not server-side glue or past data — so you recreate email sending fresh, which you'd want to do anyway given the next part.
Keep CF7 active during the import — the importer reads its database entries directly.
The five minutes that were the whole point
Once a form is across, the upgrades that close the CF7 gaps take about five minutes:
- Add a Save Submission action — now every submission is in a searchable, exportable database. No more "it probably went to spam."
- Connect real SMTP — an authenticated service (Brevo's free tier is 300/day) with a per-email delivery log: sent, failed, or bounced, recorded. The "did it arrive?" question becomes answerable.
- Behaviour-based spam scoring — runs automatically, no reCAPTCHA checkbox in front of real users.
That delivery log is the thing I actually switched for. Debugging a missing email by reading the log entry instead of interrogating the client's spam folder is a different job.
I wrote the full step-by-step — running the importer, what to verify after import (field names/slugs matter for your dynamic tags), and wiring up email templates — over on my blog:
👉 How to Migrate from Contact Form 7 to CraftForms
Disclosure: I build CraftForms, the plugin with the CF7 importer described here. The "store submissions + log delivery" principle applies whatever you migrate to.
Top comments (0)