address_unavailable
HTTP 409 retryable: false REST and MCP
The email address or domain you asked to attach is already reserved, either in this system or at the mail provider, so it cannot be claimed again.
Why it happens
Address uniqueness is enforced by a database constraint rather than by a read-then-write check, so the error surfaces as a caught unique violation on the INSERT. That is deliberate: two concurrent requests for the same address cannot both pass a prior existence check, because only one of them can win the constraint. The provider side is mapped into the same code from the other direction: the platform client treats a 409, or a 422 whose message matches already, taken, unavailable, exists or bound, as an address collision, and is careful not to treat a missing-field 422 as one.
Every condition below was read in the implementation at the file and line given. They are the branches that raise this code, not a summary of what the name suggests.
- A unique violation (SQLSTATE 23505) when attaching a custom domain to a workspace.
packages/domain/src/custom-domains.ts:263 - A unique violation on the address reservation taken before a provider inbox is requested. No provider inbox is created.
packages/domain/src/custom-domains.ts:808 - The mail provider answers 409, or 422 with a message matching already, taken, unavailable, exists or bound.
packages/mail/src/resend-platform.ts:115
How to fix it
- Pick a different local part. The address is taken and nothing about retrying changes that.
- If the domain is already in this workspace, open it from Domains rather than attaching it again. The message says so explicitly for that case.
- Check for a half-finished attempt before retrying with the same name. The reservation is taken before the provider is contacted, so an interrupted run can hold the address.
- Do not retry on a loop. This is not retryable and each attempt hits the same constraint.
Why it works this way
Reserving the exact address locally before calling the provider is what makes this recoverable. If the provider call then fails or its outcome is unknown, the address stays reserved and the inbox stays paused, so the system never ends up believing an address is free while the provider believes it is taken.
Messages you will see
The message field is written for a human reading a log and is not a contract
value. Branch on code instead. These are the exact strings this code is sent
with today, listed so that a search for one of them lands here.
Where it appears
These are the documented surfaces that raise it. The REST base is https://api.emailforagents.ai/v1, and the remote MCP endpoint is https://mcp.emailforagents.ai/mcp.
POST /projects/{project_id}/domainsPOST /projects/{project_id}/inboxes
The response
Read request_id off the body or the x-request-id header before
doing anything else. It is the only handle on one specific attempt, and for a 503 it is
the only handle at all, because the underlying detail stays server-side.
HTTP/1.1 409
content-type: application/json
x-request-id: req_...
{
"error": {
"code": "address_unavailable",
"message": "This domain cannot be attached. If it is already in this workspace, open it from Domains.",
"request_id": "req_...",
"retryable": false
}
}
Verified against packages/domain/src/custom-domains.ts:263. The status comes from ERROR_STATUS in packages/contracts/src/errors.ts, which is the one
place a code is mapped to a status.