Verified senders

Search Documentation

Search across all developer documentation

Verified senders

A verified sender is one person’s proof that they can read one mailbox. SendOps mails a six-digit code to the address, you send it back, and the claim is verified.

It exists for one purpose: temporary inboxes. An inbox whose allowed_senders are all addresses you have verified is restricted, and a restricted inbox is exempt from the mint quota and the per-credential cap. An organization that has not connected its own AWS account gets three unrestricted mints a day, so verifying one address is the difference between a feature that runs out before lunch and an unmetered supply.

The reason the exemption is safe is the same reason the code exists. A restricted inbox drops mail from anybody not on its list, so it cannot be used to collect a stranger’s signup confirmation, which is the abuse the quota is defending against.

This is not a cryptographic identity

A claim says somebody read a mailbox once. It says nothing about SPF, DKIM, or who else can send as that address, and the sender check on an inbox compares addresses only. Read verdicts.spf, verdicts.dkim and verdicts.dmarc on the message itself before treating a forwarded email as evidence of anything.

Scopes and credentials

ScopeGrants
api.inboxes.viewGET /v1/verified-senders.
api.inboxes.manageStart, confirm and revoke a claim.

Every route here needs a credential that identifies a person. A claim belongs to a person, so it works with an API key (attributed to the user who created the key) or a user-delegated OAuth token. A client-credentials token identifies nobody and is refused with 422 rather than being answered with an empty list that would read as “you have verified nothing”. See OAuth 2.1 for the user-delegated flow.

POST sends real email to an address you name

The code goes out from SendOps’ own mail service, not from your SES account, to whatever address is in the body. That is why starts are rate-limited to five an hour per user, and why api.inboxes.manage deserves thought before you grant it.

The endpoints

EndpointDoes
GET /v1/verified-sendersYour claims in this org, pending and verified both.
POST /v1/verified-sendersMail a six-digit code to an address. 202.
POST /v1/verified-senders/{id}/confirmSend the code back. 200 with verified_at set.
DELETE /v1/verified-senders/{id}Revoke the claim. 204.

The round trip

Start with a plain address you can read. A display name (You <you@example.com>) is refused, because the intake path’s sender check compares addresses and a stored display name would produce an inbox that silently matched nothing.

curl -X POST https://api.sendops.dev/v1/verified-senders \
-H "Authorization: Bearer $SENDOPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "dana@example.com"}'
{
"id": "0192c0c7-…",
"email": "dana@example.com",
"status": "pending",
"code_expires_at": "2026-09-10T14:37:00Z",
"created_at": "2026-09-10T14:22:00Z"
}

202, not 201: the row exists but the address is not verified and cannot be until somebody reads the mailbox. The code is never in a response, an error, or a log. Read it out of the email and confirm:

curl -X POST https://api.sendops.dev/v1/verified-senders/0192c0c7-…/confirm \
-H "Authorization: Bearer $SENDOPS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "418293"}'
{
"id": "0192c0c7-…",
"email": "dana@example.com",
"status": "verified",
"verified_at": "2026-09-10T14:24:31Z",
"created_at": "2026-09-10T14:22:00Z"
}

Confirming an already-verified claim is also a 200 with the same body. A second tab did nothing wrong.

Expiry, attempts, and the lock

RuleValue
Code length6 digits
Code lifetime15 minutes from when it was sent (code_expires_at)
Wrong guesses before the claim locks5
Starts per user5 an hour

A wrong code is a 422, and the detail says how many attempts remain. The fifth wrong code sets status to locked, and the way out is a fresh POST /v1/verified-senders, not a sixth guess: starting again replaces the code and resets the attempt counter. The hourly start limit is what stops that being a way around the lock.

The start limit is five an hour rather than five a day on purpose. The honest failure this has to survive is a code that never arrived: a slow relay, a spam folder, a typo the user wants to correct. Making that painful would push people away from the one action that lifts the mint quota.

Starting again on an address you have already verified gets you a new code you need not use. It does not clear verified_at, so a live restricted inbox never quietly loses its guarantee because somebody forgot they had done this.

Listing

GET /v1/verified-senders returns the claims belonging to the person the credential authenticates as, in this org, in the standard {data, pagination} envelope. Pending claims are included, because a pending claim is the answer to “did my code arrive”. status is pending, locked or verified. code_expires_at is absent on a verified claim, since confirming clears the code. Nothing here ever returns a code.

Revoking

DELETE /v1/verified-senders/{id} is a 204. It is reversible: verify the address again and you have it back, because the proof is a code sent to a mailbox you can read.

Revoking does not unrestrict inboxes already minted

An inbox’s allowed_senders is frozen at mint, and restricted records what was true at that moment. Retroactively loosening a live inbox would turn an act of tightening into one that opened somebody’s inbox to strangers. If you want an existing inbox to stop receiving, delete the inbox, which purges it immediately.

Errors worth branching on

StatusWhen
409The address is already claimed by another member of your organization. The fix is a conversation with a colleague, not a retry.
422 on startNot a plain address, or the credential is a client-credentials token, which identifies no person.
422 on confirmWrong code (the detail counts down the remaining attempts), an expired code, or a locked claim.
429A sixth start within the hour. Honour Retry-After.
404Somebody else’s claim, or one already revoked.

The dashboard equivalent

Everything on this page is also in the dashboard, under Profile → Verified senders: add an address, enter the code, revoke a claim. It is the same list the API returns, and a claim verified in one place works in the other. For a person verifying one address once, it is the shorter path.

  • Temporary inboxes: what a verified sender is for
  • OAuth 2.1: the user-delegated flow, for integrations that act as a person rather than with an API key
  • Errors: the problem document every refusal here returns