⚠️ Experimental: This project tracks the WICG Email Verification API and IETF Email Verification Protocol draft. Both are works in progress and may change before standardization.
The Email Verification Protocol enables web applications to obtain cryptographically verified email addresses without sending verification emails. Instead of the traditional "click the link in your email" flow, EVP allows instant verification when the user is already logged into their email provider.
Traditional Flow:
User → Enter email → Wait for email → Click link → Verified ❌ (slow, prone to drop-off)
EVP Flow:
User → Select email → Instant verification ✅ (seamless, no context switch)
| Package | Description | npm |
|---|---|---|
@aspect-evp/core |
Shared types, constants, and utilities | |
@aspect-evp/issuer |
EVP issuer for email providers | |
@aspect-evp/verifier |
EVP verifier for web apps (RPs) | |
@aspect-evp/cli |
CLI for testing and debugging |
# Core libraries
npm install @aspect-evp/core @aspect-evp/issuer @aspect-evp/verifier
# CLI (optional, for testing)
npm install -g @aspect-evp/cliimport {
createIssuerMiddleware,
EmailVerificationIssuer,
toResponse,
} from '@aspect-evp/issuer';
const issuer = new EmailVerificationIssuer({
issuer: 'mail.example.com',
privateKey: yourPrivateKeyJWK,
kid: '2024-01-key',
algorithm: 'EdDSA'
});
const issuance = createIssuerMiddleware(
issuer,
async (cookie, email) => {
const user = await getUserFromSession(cookie);
return user?.emails.includes(email) ?? false;
}
);
// Serve /.well-known/email-verification
app.get('/.well-known/email-verification', (req, res) => {
res.json(issuer.getMetadata('https://mail.example.com'));
});
// Serve JWKS
app.get('/email-verification/jwks', (req, res) => {
res.json(issuer.getJWKS());
});
// Fetch-style frameworks and runtimes can return this handler directly.
async function handleIssuance(request: Request): Promise<Response> {
return toResponse(await issuance.handleIssuance(request));
}import { EmailVerificationVerifier } from '@aspect-evp/verifier';
const verifier = new EmailVerificationVerifier({
rpOrigin: 'https://myapp.example.com'
});
async function handleEmailVerification(sdJwtKb: string, sessionNonce: string) {
const result = await verifier.verify(sdJwtKb, sessionNonce);
console.log('Verified email:', result.email);
console.log('Issuer:', result.issuer);
}# Generate a keypair
evp keygen -j > keys.json
# Run a complete test flow
evp test -e user@example.com -i issuer.example.com
# Inspect a token
evp inspect <token>
# Check DNS records for a domain
evp dns gmail.comUse the test utilities to exercise the protocol without depending on a native browser implementation:
import { createTestFlow } from '@aspect-evp/core/testing';
import { EmailVerificationVerifier } from '@aspect-evp/verifier';
const testFlow = await createTestFlow({
issuer: 'issuer.example.com',
rpOrigin: 'https://myapp.example.com',
});
const verifier = new EmailVerificationVerifier({
rpOrigin: 'https://myapp.example.com',
dnsResolver: testFlow.dnsResolver,
fetch: testFlow.fetch,
});
const token = await testFlow.createToken('user@example.com', 'session-nonce');
const result = await verifier.verify(token, 'session-nonce');sequenceDiagram
participant Browser as Browser (Native)
participant DNS
participant Issuer as Issuer (@aspect-evp/issuer)
participant RP as RP (@aspect-evp/verifier)
Browser->>DNS: TXT _email-verification.domain.com
DNS-->>Browser: iss=issuer.example.com
Browser->>Issuer: POST JSON + HTTP Message Signature
Issuer-->>Browser: EVT (signed JWT + ~)
Browser->>Browser: Create KB-JWT
Browser->>RP: Submit hidden form input (EVT+KB)
RP->>Issuer: Fetch JWKS
RP->>RP: Verify signatures
RP-->>Browser: Verification result
The documentation hub provides a guided path through setup, protocol flow, architecture, configuration, package APIs, testing, and development.
- Getting started
- Protocol flow
- Standards status and conformance
- Issuer guide
- Verifier guide
- Testing and 100% coverage policy
git clone https://github.com/aspect-evp/evp-js.git
cd evp-js
pnpm install
pnpm build
pnpm test
pnpm test:coverage| Command | Description |
|---|---|
pnpm build |
Build all packages |
pnpm test |
Run tests |
pnpm test:coverage |
Run tests with coverage |
pnpm lint |
Lint code |
pnpm format |
Format code |
pnpm typecheck |
Type check |
pnpm changeset |
Create a changeset for release |
| Area | Status |
|---|---|
| DNS issuer discovery and metadata | Implemented |
| JSON issuance request | Implemented |
| RFC 9421 HTTP Message Signature profile | Implemented |
EVT (typ: evt+jwt) and EVT+KB verification |
Implemented |
| Private/directed email extension points | Implemented via issuer callbacks |
| WebAuthn fallback | Challenge and verification callbacks; credential policy remains application-owned |
WICG legacy request_token helper |
Deprecated compatibility API |
A successful verification authenticates an issuer assertion bound to the RP origin, nonce, and browser key. It does not by itself prove mailbox deliverability, classify spam or disposable domains, or replace application session policy. See standards status and conformance for the precise assurance model and the rules this implementation follows.
- Node.js >= 20.0.0
- pnpm 9.x for workspace development
- WICG Email Verification API
- IETF Email Verification Protocol draft
- Chrome Intent to Prototype
- SD-JWT Specification (IETF RFC 9901)
MIT