I needed a lead engine for a small product business’s B2B outreach — not another SaaS trial and not a spreadsheet that dies after week two. So I shipped an admin-only WordPress CRM plugin with Cursor: import research CSVs, park draft emails, and only send after an administrator types a confirmation. Nothing auto-mails.
This is a build log, not a product pitch. The CRM lives on that business’s own WordPress site. The pattern is what I want on Builders: a real tool, pair-programmed with an AI coding agent, with the sharp edges left in.
What I actually shipped
- Custom plugin, Administrators only (
manage_options) — no public routes, no anonymous REST - Pipeline board + list across ten stages (Research → Ready to contact → Draft ready → Sent → … → Won / Lost / Nurture)
- CSV import for research batches with header aliasing and SHA-256 dedupe on organisation + location + email/contact
- Draft compose that parks first-batch outreach emails in Draft ready
- Send path that requires typing
SEND, then useswp_mail/ SMTP — import never sends - Activity log for draft and sent events
An offline dry run on the research folder showed why dedupe matters: hundreds of CSV rows collapsed to a much shorter list of unique leads. First-batch drafts attach to matching leads when those rows exist.
How Cursor helped (and where I stayed in the loop)
Cursor was the pair-programmer for plugin scaffolding, admin UI, importer edge cases, and the “never send on import” guardrails. I kept the product rules:
- Admin-only. If you are not a WordPress administrator, you do not see the CRM.
- No invented contacts. Only CSV / draft markdown content is stored.
- Human confirmation before any outbound. Typing
SENDis deliberate friction. - SMTP identity is separate from “looks good in the editor.” From / Reply-To and the visible signature have to match the mailbox the business actually owns.
That last one bit us. Mail hosting, MX/SPF, and “Send mail as” were not optional polish — without them the CRM is a fancy draft folder. We wired SMTP with an app password and kept testing until a From address the business wants actually left the server. The mail-auth gate that came next is in I Gate AI-Assisted Outreach on SPF, DKIM and DMARC.
What broke
- Mailbox before CRM: Missing MX/SPF meant mail to the business domain never landed. The plugin was ready; the domain was not.
- Auth vs visible From: SMTP authenticated as one mailbox while the visible From needed another. Getting that pair right took more time than the pipeline UI.
- CSV messiness: Headers differed across research batches. The importer needed aliases or every import looked “empty.”
- Temptation to auto-send: Easy to wire “import → send.” We refused. The first batch stays parked until an admin confirms.
What I left out on purpose
- No public lead form tied into this plugin (the site form can still capture separately)
- No automatic brochure PDF send
- No “AI writes the cold email and blasts hundreds of prospects” loop
If you want the Cursor-on-mustafa.net side of how I ship interactive tools, I wrote up the free local LLM hardware checker separately. For local model hardware context, the Local LLMs hub is still the front door.
Ship checklist (if you copy the pattern)
- Lock capability checks and nonces on every admin AJAX action.
- Import and send are different code paths; import must not call
wp_mail. - Prove SMTP with a real From the customer will reply to before you call the CRM “live.”
- Keep research CSVs boring and deduped — the CRM is only as honest as the sheet.
FAQ
Is this a public mustafa.net app?
No. It is a WordPress plugin on a side project’s site. This post is the build story.
Does import send email?
No. Import only creates or updates leads and can park drafts. Send requires an administrator confirmation.
Why Cursor instead of a hosted CRM?
I needed admin-gated stages, research CSV import, and send friction on the same WordPress install the site already uses — without another monthly seat tax for a tiny outbound motion.
