Skip to main content
After a user signs up (via password, OAuth with an unverified email, etc.), you usually want to confirm they own the email address. The email-verification flow uses the same primitive as magic-code sign-in, but gated on an authenticated session. It sends a 6-digit code to the user’s email field and stamps emailVerified on a successful verify. The OAuth and SSO sign-in paths stamp emailVerified automatically, because the upstream IdP already verified the email. Use this flow when the IdP did not verify the email (manual signup, edit-email flow, etc.).

Endpoints

Schema

The User entity needs an emailVerified field. Pylon writes the current ISO 8601 timestamp on success:
If your schema lacks this field, update rejects the unknown column and /email/verify returns 500 PERSIST_FAILED (with a hint to add an emailVerified datetime field to the User entity) instead of silently succeeding.

Sending a verification code

Pylon looks up the caller’s User row, reads email, creates a 6-digit code, and sends an email with subject "Verify your email address" through the configured email transport. Body: "Your email verification code is: <code>\n\nThis code will expire in 10 minutes.". Response in production:
Response in dev (PYLON_DEV_MODE=true):
Errors: The throttle is shared with magic-code sends: 1 code per email per minute. The 10-minute TTL is shared too.

Verifying

Response on success:
Pylon stamps emailVerified to the current ISO 8601 timestamp and echoes it back in the response. Errors:

Gating handlers on emailVerified

In your TS code:
In policies, project emailVerified from the User entity and reference it directly:
Pylon does not add auth.emailVerified to the policy DSL today. The field lives on your User row. You reference it through your own handlers or queries.

Security guarantees

  • Code generation, comparison, throttling shared with magic-codes — 6-digit numeric, 10-minute TTL, burn-after-5-wrong-attempts, constant-time comparison.
  • Session-gated — only the authenticated user can request and verify their own email. There is no admin override. To mark an email verified from another process, write the User row directly with admin auth.
  • ISO 8601 timestamp formatpylon_kernel::util::now_iso() produces 2026-01-15T10:30:00Z. An email is verified only when emailVerified is non-null.

Where to go next

  • Magic codes — the same 6-digit code primitive as a primary sign-in flow
  • Password — the register flow that produces an unverified email