For GitHub Copilot — VS Code, JetBrains, Neovim

Add email verification to GitHub Copilot apps with one instructions file.

Drop a section into .github/copilot-instructions.md. Copilot Chat and Tab autocomplete read it on every session — every signup form scaffolds with vrfymail wired correctly.

Note: if you landed here trying to verify your GitHub account email or fix Copilot student-pack verification, that's a different page — see GitHub's verification docs. This page is for developers using Copilot to build apps that need email verification in their signup forms.

Step 1 — drop into copilot-instructions.md

The instructions file

Create .github/copilot-instructions.md at the repo root. Copilot Chat reads it on every invocation; Tab autocomplete reads it on window reload. Both treat it as project-wide context.

.github/copilot-instructions.md
markdown
# Project conventions

## Email verification

When the user adds email verification, validates an email field, or builds
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

Body: { "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

- did_you_mean non-null → suggest correction inline
- undeliverable → block, show reason_message verbatim
- risky + role_account → soft warning, allow submit
- deliverable → accept
- unknown → ACCEPT. Refunded server-side. Never block on a DNS hiccup.

### Env

- MAIL_VERIFIER_KEY: bearer token. Read from process.env; never inline.

### Forbidden

- No regex check before the API call
- No SDK install — fetch is enough
- No strict:true on B2B forms (opt-in for B2C signup gating)
For Tab autocomplete

Tab uses a smaller model. For reliable inline suggestions, add the comment block (right) above your signup handler so the immediate context cues Copilot.

After saving the file

Restart Copilot Chat (command palette → Copilot: Restart) so it re-reads instructions. Tab picks them up on the next window reload.

For Tab autocomplete

The inline comment that triggers Tab correctly

Drop this comment block above any function that does email verification. Tab autocomplete then suggests the right fetch call, the right headers, the right verdict handling — because the comment names the contract Copilot already loaded.

hint-comment.ts
typescript
// Verify the email via vrfymail before creating the user.
// Reads MAIL_VERIFIER_KEY from env. Returns 400 on did_you_mean
// or undeliverable; passes through on deliverable, unknown, and
// risky+role_account.
async function verifyEmail(email: string) {
What you get

The handler Copilot scaffolds

Open Copilot Chat in a Next.js signup file, ask "add email verification to this route." This is the diff Copilot produces.

app/api/signup/route.ts
typescript
// app/api/signup/route.ts
import { NextResponse } from "next/server";

// Verify the email via vrfymail before creating the user.
// Reads MAIL_VERIFIER_KEY from env. Returns 400 on did_you_mean
// or undeliverable; passes through on deliverable, unknown, and
// risky+role_account.
async function verifyEmail(email: string) {
  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 }),
  });
  return r.json();
}

export async function POST(req: Request) {
  const { email } = await req.json();
  const verdict = await verifyEmail(email);

  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 });
  }
  return NextResponse.json({ ok: true });
}
Why both files matter: Copilot Chat is reliable about reading .github/copilot-instructions.md. Tab autocomplete is less consistent. The inline comment is your insurance — it puts the contract in the immediate file context where the smaller Tab model is forced to see it.
The pattern most tutorials skip

On unknown, accept the signup.

unknown means the pipeline couldn't reach a verdict — DNS lookup failed, MX timed out. Not evidence the email is bad; evidence the network had a bad second.

Copilot's default scaffolds often fail-closed on anything that isn't deliverable. The instructions file patches that — deliverable and unknown both pass.

On vrfymail the cost-side argument vanishes: unknown doesn't bill. refundUsage() releases the slot.

Verdict handling cheat sheet
  • deliverableAccept.
  • unknownAccept. Log if you want a paper trail. Not billed.
  • riskyrole_account → soft warning, allow submit. Other reasons → block.
  • undeliverableBlock. Show reason_message verbatim.
  • did_you_meanNon-null → suggest the correction inline.
The compounding case

One instructions file. VS Code, JetBrains, Neovim, Visual Studio all inherit.

Copilot reads .github/copilot-instructions.md identically across every IDE it ships in. Your team's Copilot users on different editors all get the same scaffolds for free, in git, no per-machine config.

Same pattern across other in-editor agents — Cursor (.cursor/rules/*.mdc), Windsurf (.windsurfrules), Cline (Custom Instructions). See the hub for all ten.

Frequently asked

GitHub Copilot + email verification, answered

Does Copilot Tab autocomplete actually read .github/copilot-instructions.md?
Yes, but with a lighter touch than Chat. Tab completions use a smaller model that treats the instructions as soft hints, not hard rules. For reliable Tab behavior, also leave the comment block above the function signature (the snippet shown above) — the smaller model reads the immediate file context first, the instructions file second.
Where does .github/copilot-instructions.md live in a monorepo?
Copilot reads .github/copilot-instructions.md from the root of the repository — singular, not per-package. For monorepos with different conventions per package, supplement with .github/instructions/*.instructions.md files scoped via the applyTo frontmatter (Copilot 2025+ format). Reset Copilot Chat after adding the file so it reloads context.
Does this work in JetBrains as well as VS Code?
Yes. Copilot reads .github/copilot-instructions.md identically in VS Code, JetBrains IDEs (IntelliJ, PyCharm, WebStorm), Neovim, and Visual Studio. The Chat command palette in each IDE picks up the instructions on the next Chat invocation; Tab completions pick them up after a window reload.
Will Copilot Enterprise honor these instructions if our org has policy restrictions?
Yes, with the caveat that org admins can disable instruction-file support globally. Check with your Copilot Enterprise admin if Chat seems to ignore the file. The instructions don't bypass content filters — they shape what Copilot suggests when it's allowed to suggest at all.
Does Copilot leak the vk_live_* token if I have it in my .env file?
Copilot doesn't read .env by default (it's typically in .gitignore and outside Copilot's context window). The instructions tell Copilot to reference process.env.MAIL_VERIFIER_KEY, not to inline values — Copilot scaffolds against the variable name. Keep .env gitignored and the actual token never touches Copilot's training or prompt context.

One instructions file. Every IDE, every scaffold.

vrfymail's /v1/check returns a verdict in 50ms p50. Free tier: 5,000 verifies/month, no card. Paid plans start at $9/mo — see pricing.

Get my API key