Documentation

vrfymail API

Two endpoints. JSON in, JSON out. Bearer-token auth. The full reference fits on one page.

Integration prompt for AI editors

Copy the block below and paste it into Cursor, Claude Code, Bolt, Lovable, v0, or whatever you build with. The agent will ask you for an API key first, then wire vrfymail into your codebase end-to-end. Also reachable as a plain text file at /agent-prompt.txt.

agent-prompt.md
You are helping me integrate vrfymail into this project.

vrfymail (https://vrfymail.com) is an HTTP API
that validates email addresses — disposable detection, typo correction,
role-account flagging, real-time spam-trap probes, and a per-account
bounce overlay. There is no SDK. One endpoint, JSON in, JSON out.

Follow these steps in order. Do not skip Step 1.

================================================================
Step 1 — Get my API key
================================================================

Before writing any code, say to me, verbatim:

  "Open https://vrfymail.com/dashboard, click
   'Create API key', copy the value (it starts with vk_live_), and
   paste it here."

Wait for me to paste a value that begins with `vk_live_`. Do not
continue until I do.

================================================================
Step 2 — Store the key as an environment variable
================================================================

Add the pasted key to this project's `.env` or `.env.local`:

  MAIL_VERIFIER_KEY=vk_live_<the value I pasted>

Add the same line (with no value) to `.env.example` if one exists.

If this is a Cloudflare Workers / Pages project, instead run:

  npx wrangler secret put MAIL_VERIFIER_KEY

If this is a Vercel project, run:

  vercel env add MAIL_VERIFIER_KEY

Never hardcode the key in source.

================================================================
Step 3 — Add the verify call
================================================================

Find every place this project validates an email address — signup
forms, contact forms, newsletter subscribe, anywhere `email.includes('@')`
or similar appears — and augment it with a call to /v1/check.

Use plain `fetch`. No package install.

  POST https://vrfymail.com/v1/check
  Authorization: Bearer ${MAIL_VERIFIER_KEY}
  Content-Type: application/json
  Body: { "email": "<the email>", "strict": true }

Response — the fields that matter:
  result        : "deliverable" | "undeliverable" | "risky" | "unknown"
  reason        : short string, e.g. "valid_mailbox", "disposable",
                  "typo_suspected", "role_account", "spam_trap"
  did_you_mean  : a corrected email address, or null
  account_history.matched : true if THIS api key has previously
                            reported this email as bounced

Handling:
  result === "undeliverable" → reject the form. If did_you_mean is
                                non-null, suggest the corrected
                                address: "Did you mean foo@gmail.com?"
  result === "risky"          → soft warning. Allow it through unless
                                 this is a strict B2C signup gate.
  result === "deliverable" or "unknown" → accept.

Wrap the call with a 5-second timeout. On network error or 5xx
response, fail open (treat as deliverable) so a vrfymail outage
never blocks legit signups.

================================================================
Step 4 — Optional: bounce reporting
================================================================

If this project sends transactional email via Resend, Postmark,
SendGrid, or SES, find the webhook handler for bounce + complaint
events. Add a fire-and-forget call to vrfymail whenever a bounce
arrives:

  POST https://vrfymail.com/v1/report-bounce
  Authorization: Bearer ${MAIL_VERIFIER_KEY}
  Content-Type: application/json
  Body: { "email": "<email>", "reason": "<reason>" }

Where <reason> is one of:
  hard_bounce | soft_bounce | spam_complaint | unsubscribe

Map the ESP's event types to those reasons. This builds a per-account
overlay so future verifies of the same email by the same key
short-circuit to "undeliverable" without an extra DNS probe.

================================================================
Step 5 — Optional: safe-domain allowlist
================================================================

If this project has internal/trusted domains that should NEVER be
flagged (employee email domains, partner integrations, the company's
own product domains), add them to the per-account safe-domain
allowlist. Verifies whose email's domain is on the list return:

  { result: "deliverable", reason: "customer_safelisted" }

…without running disposable / spam-trap / MX / DBL checks. Per-account
only — never affects other customers.

  GET    https://vrfymail.com/v1/safe-domains
  POST   https://vrfymail.com/v1/safe-domains
         Body: { "domain": "example.com", "note": "(optional)" }
  DELETE https://vrfymail.com/v1/safe-domains/<domain>

All three require the same `Authorization: Bearer ${MAIL_VERIFIER_KEY}`.

When to use this: only add domains the application owner has
explicitly identified as trusted. Don't auto-populate from MX records
or signup history — that defeats the purpose. Reported bounces
(/v1/report-bounce) still win at the per-email level even when the
domain is allowlisted.

================================================================
Constraints
================================================================

- No SDK install. Plain `fetch`.
- Log all errors with a `[mail-verifier]` prefix so failures are
  visible during development.
- Do not add app-side caching — vrfymail already caches per
  (api-key, email) for up to 7 days on the server side.
- Do not block email-sending paths on the verifier. Only block form
  submissions.

================================================================
Now begin
================================================================

Ask me for my API key (Step 1). Once I paste it, execute Steps 2–4
against the current codebase, adapting to whatever framework / stack
this project uses.

Authentication

Pass your API key in the Authorization header. Create one in the dashboard.

Authorization: Bearer vk_live_...

POST /v1/check

Verify a single email. Also accepts GET with ?email= and ?strict=true.

Optional body fields: strict: true opts into the strict-mode flags listed below; force: true bypasses the 7-day verify cache and re-runs the full pipeline (useful after you change settings — still bills 1 against your monthly quota).

terminal
bash
curl https://vrfymail.com/v1/check \
  -H "Authorization: Bearer vk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com", "strict": true }'

Verdicts

Reasons

POST /v1/report-bounce

Forward an ESP bounce / complaint here to build your per-customer overlay. Reasons: hard_bounce, soft_bounce, spam_complaint, unsubscribe.

terminal
bash
curl https://vrfymail.com/v1/report-bounce \
  -H "Authorization: Bearer vk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "bounced@example.com", "reason": "hard_bounce" }'

Safe domains (per-account allowlist)

Mark a domain as safe and any future verify whose email matches will short-circuit to deliverable with reason customer_safelisted — no MX, DBL, disposable, or spam-trap checks. Per-account; never affects other customers.

Reported bounces still win: if you posted /v1/report-bounce for bad@example.com, that specific email returns previously_bounced even when example.com is on your allowlist. Per-email signal beats per-domain trust.

GET /v1/safe-domains

List all domains on your allowlist. Returns { domains: [{ domain, added_at, note }] }.

terminal
bash
curl https://vrfymail.com/v1/safe-domains \
  -H "Authorization: Bearer vk_live_..."

POST /v1/safe-domains

Add a domain. Idempotent — re-posting refreshes the optional note but keeps the original added_at.

terminal
bash
curl -X POST https://vrfymail.com/v1/safe-domains \
  -H "Authorization: Bearer vk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "domain": "example.com", "note": "trusted partner" }'

DELETE /v1/safe-domains/:domain

Remove a domain. Returns 404 if the domain wasn't on the allowlist; { ok: true } otherwise.

terminal
bash
curl -X DELETE https://vrfymail.com/v1/safe-domains/example.com \
  -H "Authorization: Bearer vk_live_..."

Manage the same list interactively from /dashboard/safe-domains.

Quota + rate limits

Two separate limits apply to every call. Each can return HTTP 429 with a distinct error code.

Monthly quota

Every response carries the headers X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. These track your monthly budget. Resets at the start of each calendar month UTC. Cache hits count; cache hits of unknown results are free and refunded. Exceeding the quota returns:

{"error":{"code":"quota_exhausted","reset_at":1780272000}}

Per-minute / per-hour rate limits

A separate per-key sliding window prevents tight loops and runaway scripts from burning your monthly budget in seconds. Both windows are checked independently — exceeding either returns:

HTTP/2 429
Retry-After: 60

{
  "error": {
    "code": "rate_limited",
    "window": "per_minute",
    "retry_after_seconds": 60,
    "plan": "free"
  }
}

window is one of per_minute, per_hour, per_ip_minute, per_ip_hour.

Per-plan caps

Plan Per minute Per hour With overage
Free601,000
Indie1202,000240 / 4,000
Pro60010,0001,200 / 20,000
Business1,50030,0003,000 / 60,000

Overage variants apply automatically to paid plans with a valid payment method on file. A per-IP safety net of 600/min, 10,000/hour applies across all plans (catches free-key rotation from one IP). Caps are best-effort across Cloudflare's edge — burst tolerance is roughly 1.5-2× the published number before 429s fire.

GET /me/rate-limits

Session-authed (dashboard cookie). Returns the rate-limit caps currently in effect for your account. Useful for showing budget remaining in your own UI.

{
  "plan": "pro",
  "overage_active": false,
  "overage_available": true,
  "per_minute": 600,
  "per_hour": 10000,
  "base": { "per_minute": 600, "per_hour": 10000 }
}

overage_active reflects whether the 2× soft-burst variant is currently applied; base is the plan's published cap regardless of overage.

Session endpoints (cookie-authed)

These run off the dashboard session cookie, not a bearer token. Most integrators don't need them — they exist for the vrfymail dashboard itself. Listed for completeness.

GET /api/auth-status

Public probe. Returns { user: null } for unauthed visitors and { user: { id, email, is_admin } } when a valid session cookie is present. Always 200 OK — never 401, so marketing pages can probe auth state without surfacing console errors.

GET /me

Authenticated dashboard endpoint. Returns the full user record (plan, monthly quota state, OAuth providers, founder flag, etc). Requires a valid session cookie — returns 401 unauthenticated otherwise. Use /api/auth-status instead if you only need to know whether the visitor is signed in.