---
title: "Prove the email address before trusting it · Agent Wranglers"
url: https://agentwranglers.se/playbook/accounts/sign-up/email-verification
updated: 2026-10-20
licence: "All rights reserved"
---

# Prove the email address before trusting it

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Sign-up](https://agentwranglers.se/playbook/accounts/sign-up)
- Rule: Mark an email address as verified only after someone opens a link or types a code sent to it. Until then, send no account data there and never use it to link accounts.
- How: Store `email_verified_at` as a time, empty at sign-up. Send a link with a single-use token of at least 128 random bits, valid for 24 hours. A 6-digit code instead lasts at most 15 minutes and dies after 5 wrong tries, because a million guesses are quick. Build the token as the [sign-in links rule](https://agentwranglers.se/playbook/email/email-security/sign-in-links) says, hashed, single use and behind a button page. The link only confirms the address and never signs anyone in, so it may last longer than a sign-in link. People may use the product while unverified, but block invitations, exports, billing email and linking a provider login. Allow a resend after 60 seconds, at most 5 an hour. A verified address proves access to an inbox only, so it never replaces a sign-in factor.
- Check: Sign up with a test address and skip the link. Invite a colleague, export data and link a provider login with the same address. Each is refused. Open the link, and all three work.

Example:

```js
const NEEDS_VERIFIED_EMAIL = new Set(['invite', 'export', 'link-identity', 'billing-email']);

export function assertEmailVerified(user, action) {
  if (NEEDS_VERIFIED_EMAIL.has(action) && !user.emailVerifiedAt) {
    throw new ForbiddenError('Confirm your email address first. We sent you a link.');
  }
}
```

```python
NEEDS_VERIFIED_EMAIL = {"invite", "export", "link-identity", "billing-email"}

def assert_email_verified(user, action: str) -> None:
    if action in NEEDS_VERIFIED_EMAIL and user.email_verified_at is None:
        raise Forbidden("Confirm your email address first. We sent you a link.")
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/sign-up/email-verification`.

- Previous: [Ask only for what the first task needs](https://agentwranglers.se/playbook/accounts/sign-up/minimal-sign-up)
- Next: [Stop strangers from claiming an address before its owner](https://agentwranglers.se/playbook/accounts/sign-up/pre-hijacking)
- [Sign-up](https://agentwranglers.se/playbook/accounts/sign-up)
