api reference
Emails API
List message traffic, fetch message detail, and download raw MIME or attachment binaries.
These endpoints let you poll an inbox, read parsed message content, and download stored assets for test fixtures or workflow artifacts.
List emails
/api/emailsList emails for a single inbox using either the inbox address or address ID.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- You must provide either address or addressId.
- after and before accept either millisecond timestamps or values parseable by Date.parse().
- search is full-text and does not support after, before, or order=asc.
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Query parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
address | string | Optional | Normalized inbox email address to query. Required when addressId is omitted. |
addressId | string | Optional | Inbox identifier to query. Required when address is omitted. |
limit | string | Optional | Maximum number of emails to return. Default: Constraints: Clamped to 1-100. |
order | "asc" | "desc" | Optional | Sort order by receivedAt. Default: |
after | string | Optional | Lower time bound for email receivedAt filtering. |
before | string | Optional | Upper time bound for email receivedAt filtering. |
search | string | Optional | Full-text search over stored email content and indexed metadata for the target inbox. Constraints: Trimmed length up to 30 characters. |
Success response
| Field | Type | Details |
|---|---|---|
address | string | Normalized inbox address that matched the request. |
addressId | string | Inbox identifier that matched the request. |
items | Array<object> | Email list for the inbox. |
items[].id | string | Email identifier. |
items[].addressId | string | Inbox identifier that received the message. |
items[].to | string | Resolved recipient address stored for the email. |
items[].from | string | Raw sender value from the message envelope or headers. |
items[].sender | string | null | Stored sender identity value when available, typically including display name and address. |
items[].senderLabel | string | Display-friendly sender label derived from sender or from. |
items[].subject | string | null | Parsed subject line when present. |
items[].messageId | string | null | Parsed Message-ID header when available. |
items[].rawSize | number | null | Stored raw MIME size in bytes when tracked. |
items[].rawTruncated | boolean | True when the stored raw source exceeded configured limits. |
items[].isSample | boolean | True when the email was generated as a starter sample message. |
items[].hasHtml | boolean | Whether an HTML body was stored for the email. |
items[].hasText | boolean | Whether a text body was stored for the email. |
items[].attachmentCount | number | Number of stored attachments linked to the email. |
items[].receivedAt | string | null | When the email was accepted into the inbox. Constraints: ISO 8601 timestamp when present. |
items[].receivedAtMs | number | null | Millisecond representation of receivedAt. Constraints: Unix timestamp in milliseconds when present. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | address or addressId is required | Neither address nor addressId is provided. |
400 | search does not support after, before, or order=asc parameters | search is combined with unsupported time filters or ascending order. |
404 | address not found | The supplied address or addressId does not exist in the current organization. |
Example request
curl --get "https://api.spinupmail.com/api/emails" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--data-urlencode "addressId=addr_123" \
--data-urlencode "limit=20" \
--data-urlencode "order=desc"Example response
{
"address": "signup-test@spinupmail.dev",
"addressId": "addr_123",
"items": [
{
"id": "mail_123",
"addressId": "addr_123",
"to": "signup-test@spinupmail.dev",
"from": "notifications@github.com",
"sender": ""GitHub" <notifications@github.com>",
"senderLabel": "GitHub",
"subject": "Verify your email",
"messageId": "<abc@example.com>",
"rawSize": 13821,
"rawTruncated": false,
"isSample": false,
"hasHtml": true,
"hasText": true,
"attachmentCount": 1,
"receivedAt": "2026-03-08T09:45:10.000Z",
"receivedAtMs": 1772963110000
}
]
}Get email detail
/api/emails/:idFetch the parsed email body, headers, and attachment metadata for a single stored email.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- raw is opt-in. If raw is omitted, the response excludes the raw field even when raw storage exists.
- rawDownloadPath is present when the service can serve raw MIME from D1 or R2.
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Path parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
id | string | Required | Email identifier. |
Query parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
raw | string | Optional | Set to 1 or true to include the stored raw MIME content inline in the JSON response. |
Success response
| Field | Type | Details |
|---|---|---|
id | string | Email identifier. |
addressId | string | Inbox identifier. |
address | string | Inbox email address. |
to | string | Resolved recipient address. |
from | string | Raw sender value. |
sender | string | null | Stored sender identity value when available. |
senderLabel | string | Display-friendly sender label derived from sender data. |
subject | string | null | Parsed subject line. |
messageId | string | null | Parsed Message-ID header. |
headers | unknown | Parsed header structure. Falls back to an empty array when stored headers cannot be parsed. |
html | string | null | Stored HTML body, when available. |
text | string | null | Stored text body, when available. |
raw | string | null | Included only when raw=1 or raw=true is requested and raw content is stored in D1. |
rawSize | number | null | Stored raw MIME size in bytes when tracked. |
rawTruncated | boolean | Whether the stored raw payload was truncated. |
isSample | boolean | True when the email was generated as a starter sample message. |
rawDownloadPath | string | Relative API path for raw MIME download when a raw source is available from D1 or R2. |
attachments | Array<object> | Stored attachment metadata. |
attachments[].id | string | Attachment identifier. |
attachments[].filename | string | Original attachment filename after sanitization for downloads. |
attachments[].contentType | string | Detected attachment MIME type. |
attachments[].size | number | Attachment size in bytes. |
attachments[].disposition | string | null | Content-Disposition header value when available. |
attachments[].contentId | string | null | Content-ID value for inline attachments when available. |
attachments[].downloadPath | string | Relative API path for the attachment download endpoint. |
attachments[].inlinePath | string | Relative API path that requests inline rendering for safe image attachments. |
receivedAt | string | null | When the email was accepted. Constraints: ISO 8601 timestamp when present. |
receivedAtMs | number | null | Millisecond representation of receivedAt. Constraints: Unix timestamp in milliseconds when present. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
404 | email not found | The requested email does not exist in the current organization. |
Example request
curl --get "https://api.spinupmail.com/api/emails/mail_123" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--data-urlencode "raw=1"Example response
{
"id": "mail_123",
"addressId": "addr_123",
"address": "signup-test@spinupmail.dev",
"to": "signup-test@spinupmail.dev",
"from": "notifications@github.com",
"sender": ""GitHub" <notifications@github.com>",
"senderLabel": "GitHub",
"subject": "Verify your email",
"messageId": "<abc@example.com>",
"headers": [{ "name": "from", "value": "notifications@github.com" }],
"html": "<p>Verify your email</p>",
"text": "Verify your email",
"raw": "From: notifications@github.com\n...",
"rawSize": 13821,
"rawTruncated": false,
"isSample": false,
"rawDownloadPath": "/api/emails/mail_123/raw",
"attachments": [
{
"id": "att_987",
"filename": "receipt.pdf",
"contentType": "application/pdf",
"size": 48211,
"disposition": "attachment",
"contentId": null,
"downloadPath": "/api/emails/mail_123/attachments/att_987",
"inlinePath": "/api/emails/mail_123/attachments/att_987?inline=1"
}
],
"receivedAt": "2026-03-08T09:45:10.000Z",
"receivedAtMs": 1772963110000
}Download raw email
/api/emails/:id/rawDownload the raw MIME source for an email when raw storage is available in D1 or R2.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- The response is a binary/text download, not JSON, on success.
- raw download availability depends on whether raw MIME was stored in D1 or R2.
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Path parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
id | string | Required | Email identifier. |
Success response
| Field | Type | Details |
|---|---|---|
Content-Type | message/rfc822 | Raw MIME content type. R2-backed responses may use stored HTTP metadata instead. |
Content-Disposition | attachment | Download filename in the form <emailId>.eml. |
Cache-Control | string | private, max-age=0, must-revalidate |
Common error responses
| Status | Error | When it happens |
|---|---|---|
404 | email not found | The requested email does not exist in the current organization. |
404 | raw source not available | The email exists but no raw MIME source is available from D1 or R2. |
Example request
curl -L "https://api.spinupmail.com/api/emails/mail_123/raw" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--output mail_123.emlDownload attachment
/api/emails/:id/attachments/:attachmentIdStream a single attachment binary from R2 for a stored email.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- Attachment download requires R2_BUCKET to be configured.
- Successful responses stream binary content with attachment headers instead of JSON.
- inline rendering is only allowed for safe inline image content types.
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Path parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
id | string | Required | Email identifier. |
attachmentId | string | Required | Attachment identifier. |
Query parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
inline | string | Optional | Set to 1 or true to request inline rendering for safe image attachments. |
Success response
| Field | Type | Details |
|---|---|---|
Content-Type | string | Attachment MIME type or application/octet-stream fallback. |
Content-Disposition | attachment | Sanitized filename for the downloaded attachment. |
Content-Length | string | Attachment size in bytes. |
Cache-Control | string | private, max-age=0, must-revalidate |
Common error responses
| Status | Error | When it happens |
|---|---|---|
503 | Attachment storage is not configured | R2_BUCKET is not bound in the Worker environment. |
404 | Attachments are disabled | EMAIL_ATTACHMENTS_ENABLED is false for the Worker. |
404 | attachment not found | The attachment metadata does not exist in the current organization. |
415 | attachment content cannot be rendered inline | inline rendering is requested for a non-inline-safe attachment type. |
404 | attachment content not found | Attachment metadata exists but the R2 object cannot be found. |
Example request
curl -L "https://api.spinupmail.com/api/emails/mail_123/attachments/att_987" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--output receipt.pdfDelete email
/api/emails/:idDelete a stored email, its attachment metadata, and the related raw/attachment R2 objects when present.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Path parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
id | string | Required | Email identifier to delete. |
Success response
| Field | Type | Details |
|---|---|---|
id | string | Deleted email identifier. |
deleted | boolean | Always true on success. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
404 | email not found | The requested email does not exist in the current organization. |
500 | failed to clean up email files | The email exists but R2 cleanup fails before deletion completes. |
Example request
curl -X DELETE "https://api.spinupmail.com/api/emails/mail_123" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123"Example response
{
"id": "mail_123",
"deleted": true
}
