attachment_not_ready
HTTP 422 retryable: false REST and MCP
The attachment exists but its scan has not completed cleanly, so it cannot be sent or downloaded.
Why it happens
A file is usable only when every one of several states lines up: the attachment row is ready, its scan_status is clean, the underlying object is ready, and the recorded SHA-256 matches the object registry's digest. That last comparison is the interesting one. It means a file whose stored bytes no longer hash to what was scanned is treated as not ready, so a clean scan result cannot be inherited by different content. On the download side the same code covers a file that was purged or rejected.
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.
- Downloading a file whose scan_status is not clean, or whose status is purged or rejected.
packages/domain/src/services.ts:3849 - Sending with an attachment that is not ready, not scanned clean, whose object is not ready, or whose recorded hash does not match the object registry digest.
packages/domain/src/outbound-files.ts:19
How to fix it
- Poll the upload with GET /projects/{project_id}/uploads/{upload_id} until it reports a clean scan, then send. Scanning is asynchronous and completing the upload does not complete the scan.
- Complete the upload before attaching it. Reserve, PUT the content, then call complete; an attachment that skipped completion never reaches a scannable state.
- If the scan rejected the file, do not retry with the same bytes. A rejected file stays rejected.
- Check that you are attaching the upload id, not the object id or the message id.
Why it works this way
Comparing the recorded hash against the object registry digest at send time, rather than trusting the scan flag alone, is what stops a clean verdict from being transferable to content that was swapped underneath it.
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.
GET /projects/{project_id}/uploads/{upload_id}POST /projects/{project_id}/uploads/{upload_id}/completePOST /projects/{project_id}/inboxes/{inbox_id}/messages
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 422
content-type: application/json
x-request-id: req_...
{
"error": {
"code": "attachment_not_ready",
"message": "This file cannot be downloaded until a successful scan marks it clean.",
"request_id": "req_...",
"retryable": false
}
}
Verified against packages/domain/src/services.ts:3851. The status comes from ERROR_STATUS in packages/contracts/src/errors.ts, which is the one
place a code is mapped to a status.