Add email verification to Claude Code apps in one CLAUDE.md section.
Drop a section into CLAUDE.md at your repo root. Every signup form Claude Code scaffolds afterward — signup, password reset, contact, team invite — speaks the same vrfymail verdict contract.
Note: if you landed here trying to log into your Anthropic account (the "verification code didn't arrive" problem), that's a different page — see Anthropic's support docs. This page is for developers using Claude Code to build apps that need email verification in their signup forms.
The CLAUDE.md section
Claude Code reads CLAUDE.md at your repo root as project context for every session. Paste this section under your existing External services or API integrations heading. Commit it.
## Email verification
When asked to add email verification, validate an email field, or build a
signup form, use vrfymail's /v1/check API. Never write a regex-only validator.
### Endpoint
```
POST https://vrfymail.com/v1/check
Authorization: Bearer ${process.env.MAIL_VERIFIER_KEY}
Content-Type: application/json
{ "email": "<email>", "strict": true }
```
### Response shape
```
{
"result": "deliverable" | "undeliverable" | "risky" | "unknown",
"reason": "<machine code, e.g. role_account>",
"reason_message": "<end-user copy already mapped>",
"did_you_mean": "<typo correction, or null>"
}
```
### Handling rules
- `did_you_mean` non-null → suggest the correction inline.
- `undeliverable` → block, show `reason_message` verbatim.
- `risky` with reason `role_account` → soft warning, allow submit.
- `deliverable` → accept.
- `unknown` → ACCEPT. Never block real users on a DNS hiccup; the call
is refunded server-side, so it doesn't cost a credit either.
### Env vars
- `MAIL_VERIFIER_KEY` — bearer token, format `vk_live_*`. Never inline.
### Forbidden patterns
- No regex check before the API call — syntax mistakes are caught by
`reason: "syntax"` inside vrfymail.
- No SDK install. There isn't one — fetch is enough.
- No `strict: true` by default for B2B forms. Strict mode is opt-in for
B2C signup gating. Drop into ./CLAUDE.md at the repo root. Travels with the project; new contributors get it on clone.
Drop into ~/.claude/CLAUDE.md. Inherited across every project Claude Code touches on your machine.
/verify-email for one-off checks
For testing an address from the CLI without opening any project, drop a slash command into .claude/commands/. Invoke as /verify-email user@example.com in any Claude Code session.
# Verify an email address
Verify an email address against vrfymail and report the verdict.
## Usage
/verify-email [address]
## Steps
1. POST https://vrfymail.com/v1/check with Authorization header from `$MAIL_VERIFIER_KEY`
and body `{"email": "<address>", "strict": false}`.
2. Print the `result`, `reason`, and `reason_message` fields.
3. If `did_you_mean` is non-null, suggest the correction.
4. Exit code 0 for deliverable/unknown, 1 for undeliverable/risky. The route handler Claude Code scaffolds
Given the CLAUDE.md section, this is the diff Claude Code produces in a Next.js project when you ask "add email verification to the signup form." Adapts to Hono, Express, FastAPI, whatever your project runs.
// app/api/signup/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const { email } = await req.json();
const r = await fetch("https://vrfymail.com/v1/check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.MAIL_VERIFIER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email, strict: true }),
});
const verdict = await r.json();
if (verdict.did_you_mean) {
return NextResponse.json(
{ error: `Did you mean ${verdict.did_you_mean}?`, suggestion: verdict.did_you_mean },
{ status: 400 }
);
}
if (verdict.result === "undeliverable") {
return NextResponse.json(
{ error: verdict.reason_message, code: verdict.reason },
{ status: 400 }
);
}
if (verdict.result === "risky" && verdict.reason === "role_account") {
return NextResponse.json({ ok: true, warn: verdict.reason_message });
}
// "deliverable" and "unknown" both fall through to accept.
return NextResponse.json({ ok: true });
} valid (which isn't a verdict).
On unknown, accept the signup.
unknown is what you get when the verification pipeline couldn't reach a verdict in the time budget. DNS lookup failed. MX timed out. None of those are evidence the email is bad — they're evidence the network had a bad second.
If you fail-closed on unknown, a real customer whose corporate DNS resolver hiccups for 800ms hits your form, gets rejected, and bounces. The CLAUDE.md section encodes the right pattern: log it, accept the signup, let downstream bounce-handling catch real problems.
On vrfymail the cost-side argument vanishes too: unknown verdicts don't bill. refundUsage() releases the slot when the pipeline can't reach a verdict.
- deliverable Accept.
- unknown Accept. Log the verdict if you want a paper trail. Not billed.
- risky
role_account→ soft warning, allow submit. Other reasons → block or quarantine. - undeliverable Block. Show
reason_messageverbatim. - did_you_mean Non-null → suggest the correction inline. Works on any verdict.
One CLAUDE.md section, every form scaffolds the same way.
Claude Code is built around long-horizon tasks. Refactors that span a week. Multi-file edits across an entire feature branch. The longer the horizon, the more important it is that scaffolded code stays internally consistent.
A CLAUDE.md section is the durable answer. It lives next to the code, travels in git, gets read at the start of every session. The engineer who knows the verdict contract isn't always the one on the keyboard — the section makes sure the keyboard doesn't matter.
The same pattern works across other AI coding tools — Cursor (drop it into .cursor/rules/email-verify.mdc), v0 (project prompt), and seven others on the AI builders hub.
Claude Code + email verification, answered
- Where exactly should the section go in CLAUDE.md?
- Anywhere in the file — Claude Code reads the whole CLAUDE.md as project context. A reasonable home is under a top-level '## External services' or '## API integrations' heading so the section sits next to your other service contracts. If you have nested CLAUDE.md files (one per directory), put it in the closest one to your auth/signup code — Claude Code prefers the deepest match.
- Do I also need the /verify-email slash command, or is CLAUDE.md enough?
- CLAUDE.md alone is enough for code generation — the slash command is a convenience for one-off verification checks from the CLI. If you want to test an address without opening a project (`/verify-email user@example.com`), drop the slash command into .claude/commands/. If not, skip it.
- Does this work with Claude Code's --print mode or only interactive sessions?
- Both. The CLAUDE.md section is part of project context regardless of how Claude Code is invoked. Headless one-shot runs (`claude -p "add email verification"`) read CLAUDE.md before producing the diff, same as interactive sessions.
- Will Claude Code leak my vk_live_* key into the prompt?
- Not if you reference it via the env var. The CLAUDE.md section names $MAIL_VERIFIER_KEY explicitly — Claude Code scaffolds against the variable, not against a hard-coded value. Keep the actual bearer token in .env (gitignored) or your deployment platform's secret store; don't paste it into the prompt window.
- Can I share this CLAUDE.md across multiple projects?
- Yes — put the section in your global ~/.claude/CLAUDE.md (user-level) instead of the project root, and every Claude Code session inherits it. Project-level CLAUDE.md is preferred when only some of your projects need email verification; user-level is right if every project you touch will eventually wire it.
One section. Every future signup form, correct.
vrfymail's /v1/check returns a verdict in 50ms p50. Free tier: 5,000 verifies/month, no card. Paid plans start at $9/mo for 10,000 — see pricing.