api reference
Email Addresses API
Create, list, update, and delete inboxes with TTL, sender policy, retention controls, and integration subscriptions.
The address API is where most automation starts. It lets you provision an inbox, constrain who can send to it, limit how long it lives, and clean it up when the workflow ends.
If the Worker is configured with FORCED_MAIL_PREFIX, create and rename
operations always apply that prefix on the backend before the final address is
validated and stored.
Email retention is controlled by two limits:
/api/domainsreturnsmaxReceivedEmailsPerAddressandmaxReceivedEmailsPerOrganizationso clients can mirror the active limits.- Address create and update requests still use
maxReceivedEmailCountandmaxReceivedEmailActionfor the per-inbox policy stored in metadata. - The effective inbox retention limit is capped by the current
maxReceivedEmailsPerAddressvalue, and inbound storage is also bounded by the organization-widemaxReceivedEmailsPerOrganizationtotal.
Address endpoints also expose integration-linked fields:
integrationSubscriptionscan be provided on create and update requests.integrationsis returned on address list/detail/create/update responses.
Integration subscriptions
Spinupmail integrations let you route inbox events to external providers.
Telegram is currently available, and each address can subscribe to integration
events through integrationSubscriptions.
Use this when you want workflow inboxes (for sign-up tests, operations, or QA) to notify your team in real time without polling the dashboard.
{
"localPart": "alerts",
"integrationSubscriptions": [
{
"integrationId": "int_telegram_123",
"eventType": "email.received"
}
]
}For provider setup, delivery behavior, and troubleshooting, see Integrations.
List email addresses
/api/email-addressesList addresses for the current organization with pagination and sorting.
- 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. |
Query parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
page | string | Optional | 1-based page number. Default: |
pageSize | string | Optional | Number of items per page. Default: Constraints: Clamped to 1-50. |
search | string | Optional | Case-insensitive address search string applied to the organization inbox list. |
sortBy | "createdAt" | "address" | "lastReceivedAt" | Optional | Sort field. Default: |
sortDirection | "asc" | "desc" | Optional | Sort direction. Default: |
Success response
| Field | Type | Details |
|---|---|---|
items | Array<object> | Page of address records. |
items[].id | string | Spinupmail address identifier. |
items[].address | string | Fully qualified inbox address in normalized lowercase form. |
items[].localPart | string | Normalized inbox local part stored for the address. |
items[].domain | string | Configured inbound domain assigned to the address. |
items[].meta | unknown | Parsed address metadata. May be an object, string, or null depending on what was stored. |
items[].integrations | Array<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }> | Active integration subscriptions currently attached to this inbox. |
items[].emailCount | number | Number of stored emails currently linked to the inbox. |
items[].allowedFromDomains | string[] | Normalized allowlist of sender domains extracted from metadata. |
items[].blockedSenderDomains | string[] | Normalized denylist of sender domains extracted from metadata. |
items[].inboundRatePolicy | object | null | Optional inbound abuse policy extracted from metadata. Null when no custom policy is set. |
items[].maxReceivedEmailCount | number | null | Maximum number of emails this inbox may retain before the configured action applies. |
items[].maxReceivedEmailAction | "cleanAll" | "dropNew" | null | Behavior applied when maxReceivedEmailCount is reached. dropNew accepts and discards additional mail without sender rejection. Returns null when no count limit is active. |
items[].createdAt | string | null | When the address record was created. Constraints: ISO 8601 timestamp when present. |
items[].createdAtMs | number | null | Millisecond representation of createdAt. Constraints: Unix timestamp in milliseconds when present. |
items[].expiresAt | string | null | When the inbox expires if a TTL was configured. Constraints: ISO 8601 timestamp when present. |
items[].expiresAtMs | number | null | Millisecond representation of expiresAt. Constraints: Unix timestamp in milliseconds when present. |
items[].lastReceivedAt | string | null | When the inbox most recently received an email. Constraints: ISO 8601 timestamp when present. |
items[].lastReceivedAtMs | number | null | Millisecond representation of lastReceivedAt. Constraints: Unix timestamp in milliseconds when present. |
page | number | Current page number. |
pageSize | number | Resolved page size. |
totalItems | number | Total addresses in the organization. |
addressLimit | number | Configured per-organization address cap from MAX_ADDRESSES_PER_ORGANIZATION. |
totalPages | number | Total number of pages at the current page size. |
sortBy | "createdAt" | "address" | "lastReceivedAt" | Resolved sort field. |
sortDirection | "asc" | "desc" | Resolved sort direction. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | x-org-id header is required for api key usage | An API key request omits X-Org-Id. |
400 | active organization is required | A session request has no active organization and does not pass X-Org-Id. |
401 | unauthorized | The request is not authenticated. |
403 | forbidden | The authenticated principal does not belong to the requested organization. |
Example request
curl --get "https://api.spinupmail.com/api/email-addresses" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--data-urlencode "page=1" \
--data-urlencode "pageSize=20" \
--data-urlencode "sortBy=createdAt" \
--data-urlencode "sortDirection=desc"Example response
{
"items": [
{
"id": "addr_123",
"address": "signup-test@spinupmail.dev",
"localPart": "signup-test",
"domain": "spinupmail.dev",
"meta": {
"allowedFromDomains": ["github.com"],
"blockedSenderDomains": ["spam.test"],
"maxReceivedEmailCount": 25,
"maxReceivedEmailAction": "dropNew"
},
"integrations": [
{
"id": "sub_telegram_456",
"provider": "telegram",
"name": "Ops Alerts",
"eventType": "email.received"
}
],
"emailCount": 3,
"allowedFromDomains": ["github.com"],
"blockedSenderDomains": ["spam.test"],
"inboundRatePolicy": null,
"maxReceivedEmailCount": 25,
"maxReceivedEmailAction": "dropNew",
"createdAt": "2026-03-08T09:00:00.000Z",
"createdAtMs": 1772960400000,
"expiresAt": "2026-03-08T11:00:00.000Z",
"expiresAtMs": 1772967600000,
"lastReceivedAt": "2026-03-08T09:45:10.000Z",
"lastReceivedAtMs": 1772963110000
}
],
"page": 1,
"pageSize": 20,
"totalItems": 1,
"addressLimit": 100,
"totalPages": 1,
"sortBy": "createdAt",
"sortDirection": "desc"
}List recent address activity
/api/email-addresses/recent-activityReturn a cursor-paginated activity feed of the most recently active inboxes for the current organization.
- 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. |
Query parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
limit | string | Optional | Number of results to return. Default: Constraints: Clamped to 1-50. |
cursor | string | Optional | Opaque pagination cursor returned by the previous response. |
search | string | Optional | Case-insensitive address search string applied before recent-activity ordering. |
sortBy | "recentActivity" | "createdAt" | Optional | Sort field for the recent-activity feed. Default: |
sortDirection | "asc" | "desc" | Optional | Sort direction for the recent-activity feed. Default: |
Success response
| Field | Type | Details |
|---|---|---|
items | Array<object> | Address records ordered by recent activity. |
items[].id | string | Spinupmail address identifier. |
items[].address | string | Fully qualified inbox address in normalized lowercase form. |
items[].localPart | string | Normalized inbox local part stored for the address. |
items[].domain | string | Configured inbound domain assigned to the address. |
items[].meta | unknown | Parsed address metadata. May be an object, string, or null depending on what was stored. |
items[].integrations | Array<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }> | Active integration subscriptions currently attached to this inbox. |
items[].emailCount | number | Number of stored emails currently linked to the inbox. |
items[].allowedFromDomains | string[] | Normalized allowlist of sender domains extracted from metadata. |
items[].blockedSenderDomains | string[] | Normalized denylist of sender domains extracted from metadata. |
items[].inboundRatePolicy | object | null | Optional inbound abuse policy extracted from metadata. Null when no custom policy is set. |
items[].maxReceivedEmailCount | number | null | Maximum number of emails this inbox may retain before the configured action applies. |
items[].maxReceivedEmailAction | "cleanAll" | "dropNew" | null | Behavior applied when maxReceivedEmailCount is reached. dropNew accepts and discards additional mail without sender rejection. Returns null when no count limit is active. |
items[].createdAt | string | null | When the address record was created. Constraints: ISO 8601 timestamp when present. |
items[].createdAtMs | number | null | Millisecond representation of createdAt. Constraints: Unix timestamp in milliseconds when present. |
items[].expiresAt | string | null | When the inbox expires if a TTL was configured. Constraints: ISO 8601 timestamp when present. |
items[].expiresAtMs | number | null | Millisecond representation of expiresAt. Constraints: Unix timestamp in milliseconds when present. |
items[].lastReceivedAt | string | null | When the inbox most recently received an email. Constraints: ISO 8601 timestamp when present. |
items[].lastReceivedAtMs | number | null | Millisecond representation of lastReceivedAt. Constraints: Unix timestamp in milliseconds when present. |
nextCursor | string | null | Cursor to request the next page or null when exhausted. |
totalItems | number | Total matching inbox count before cursor pagination is applied. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | invalid cursor | cursor is provided but cannot be decoded by the service. |
400 | x-org-id header is required for api key usage | An API key request omits X-Org-Id. |
400 | active organization is required | A session request has no active organization and does not pass X-Org-Id. |
401 | unauthorized | The request is not authenticated. |
403 | forbidden | The authenticated principal does not belong to the requested organization. |
Example request
curl --get "https://api.spinupmail.com/api/email-addresses/recent-activity" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--data-urlencode "limit=10"Example response
{
"items": [
{
"id": "addr_123",
"address": "signup-test@spinupmail.dev",
"localPart": "signup-test",
"domain": "spinupmail.dev",
"meta": null,
"integrations": [],
"emailCount": 4,
"allowedFromDomains": [],
"blockedSenderDomains": [],
"inboundRatePolicy": null,
"maxReceivedEmailCount": null,
"maxReceivedEmailAction": null,
"createdAt": "2026-03-08T09:00:00.000Z",
"createdAtMs": 1772960400000,
"expiresAt": null,
"expiresAtMs": null,
"lastReceivedAt": "2026-03-08T09:45:10.000Z",
"lastReceivedAtMs": 1772963110000
}
],
"nextCursor": "1772963110000:addr_123",
"totalItems": 8
}Create an email address
/api/email-addressesCreate a new inbox under the current organization with TTL, sender restrictions, inbox size controls, and optional integration subscriptions.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- If the Worker sets FORCED_MAIL_PREFIX, the backend prepends <prefix>- to localPart before reserved-word checks, conflict checks, and persistence.
- GET /api/domains exposes the live maxReceivedEmailsPerAddress and maxReceivedEmailsPerOrganization values for client-side defaults and validation hints.
- integrationSubscriptions currently supports only email.received and can be managed only by organization admins.
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. |
Content-Type | application/json | Required | JSON request body is required. |
Request body
| Field | Type | Requirement | Details |
|---|---|---|---|
localPart | string | Required | Inbox local part before normalization. Constraints: 1-30 characters after trim, letters/numbers/dot/underscore/plus/dash only. Reserved inbox keywords are rejected. If FORCED_MAIL_PREFIX is configured, the backend prepends <prefix>- before final validation and storage. |
ttlMinutes | number | Optional | Inbox lifetime in minutes. Constraints: Whole number between 1 and 43200. |
meta | unknown | Optional | Optional metadata. If you use allowedFromDomains or maxReceivedEmailCount, meta must resolve to an object or JSON object string. |
domain | string | Optional | Domain to assign. Defaults to the first configured Worker domain. |
integrationSubscriptions | Array<{ integrationId: string; eventType: "email.received" }> | Optional | Optional integration subscriptions to attach to the inbox at create time. Constraints: Only organization admins can set this field. Each entry must reference an active integration in the same organization. |
allowedFromDomains | string[] | string | Optional | Sender-domain allowlist. Strings are split on commas and normalized to lowercase unique domains. Constraints: Maximum 10 domains, each at most 50 characters. |
blockedSenderDomains | string[] | string | Optional | Sender-domain denylist. Strings are split on commas and normalized to lowercase unique domains. Constraints: Maximum 50 domains, each at most 50 characters. |
inboundRatePolicy | object | Optional | Optional inbound abuse control object with positive whole-number limits such as senderDomainSoftMax, senderDomainBlockMax, senderAddressBlockMax, inboxBlockMax, dedupeWindowSeconds, initialBlockSeconds, and maxBlockSeconds. Constraints: Must contain at least one supported positive whole-number field. Each value is capped at 864000. |
maxReceivedEmailCount | number | Optional | Requested per-inbox retention limit before taking action. Constraints: Whole number between 1 and 100000. When omitted, the backend stores the current MAX_RECEIVED_EMAILS_PER_ADDRESS default. Effective runtime retention is still capped by the live /api/domains maxReceivedEmailsPerAddress value. |
maxReceivedEmailAction | "cleanAll" | "dropNew" | Optional | Action applied when maxReceivedEmailCount is reached. dropNew accepts and discards additional mail without sender rejection. When omitted, the backend stores cleanAll. Default: |
acceptedRiskNotice | boolean | Required | Explicit acknowledgement required by the API before creating an inbox. Constraints: Must be true. |
Success response
| Field | Type | Details |
|---|---|---|
id | string | Spinupmail address identifier. |
address | string | Fully qualified inbox address in normalized lowercase form. |
localPart | string | Normalized inbox local part stored for the address. |
domain | string | Configured inbound domain assigned to the address. |
meta | unknown | Parsed address metadata. May be an object, string, or null depending on what was stored. |
integrations | Array<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }> | Active integration subscriptions currently attached to this inbox. |
emailCount | number | Number of stored emails currently linked to the inbox. |
allowedFromDomains | string[] | Normalized allowlist of sender domains extracted from metadata. |
blockedSenderDomains | string[] | Normalized denylist of sender domains extracted from metadata. |
inboundRatePolicy | object | null | Optional inbound abuse policy extracted from metadata. Null when no custom policy is set. |
maxReceivedEmailCount | number | null | Maximum number of emails this inbox may retain before the configured action applies. |
maxReceivedEmailAction | "cleanAll" | "dropNew" | null | Behavior applied when maxReceivedEmailCount is reached. dropNew accepts and discards additional mail without sender rejection. Returns null when no count limit is active. |
createdAt | string | null | When the address record was created. Constraints: ISO 8601 timestamp when present. |
createdAtMs | number | null | Millisecond representation of createdAt. Constraints: Unix timestamp in milliseconds when present. |
expiresAt | string | null | When the inbox expires if a TTL was configured. Constraints: ISO 8601 timestamp when present. |
expiresAtMs | number | null | Millisecond representation of expiresAt. Constraints: Unix timestamp in milliseconds when present. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | invalid request body | The JSON body does not satisfy schema validation. |
400 | acceptedRiskNotice must be true | acceptedRiskNotice is missing or false. |
400 | EMAIL_DOMAINS is not configured | The Worker has no configured domains. |
400 | domain is invalid | domain is malformed or contains @. |
400 | domain is not allowed | domain is not one of the configured Worker domains. |
400 | integrationSubscriptions must reference active integrations in the current organization | integrationSubscriptions includes an unknown, archived, or out-of-organization integration. |
400 | allowedFromDomains contains invalid domain(s) | One or more sender allowlist entries are not valid domain hostnames. |
400 | blockedSenderDomains contains invalid domain(s) | One or more sender denylist entries are not valid domain hostnames. |
400 | inboundRatePolicy must be an object with at least one positive whole-number limit | inboundRatePolicy is present but does not resolve to a supported policy object. |
400 | localPart is required and may only contain letters, numbers, dot, underscore, plus, and dash | localPart normalizes to an empty or invalid value. |
400 | localPart is reserved and cannot be used | localPart matches a reserved inbox keyword. |
403 | Only organization admins can manage integrations | integrationSubscriptions is provided by a non-admin member. |
409 | Address already exists | Another address already uses the same normalized address. |
409 | Address limit reached. Each organization can create up to <limit> addresses. | The organization has reached MAX_ADDRESSES_PER_ORGANIZATION. |
Example request
curl -X POST "https://api.spinupmail.com/api/email-addresses" \
-H "Content-Type: application/json" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
-d '{
"localPart": "signup-test",
"domain": "spinupmail.dev",
"ttlMinutes": 120,
"integrationSubscriptions": [
{
"integrationId": "int_telegram_123",
"eventType": "email.received"
}
],
"allowedFromDomains": ["github.com", "example.com"],
"blockedSenderDomains": ["spam.test"],
"maxReceivedEmailCount": 25,
"maxReceivedEmailAction": "dropNew",
"acceptedRiskNotice": true
}'Example response
{
"id": "addr_123",
"address": "temp-signup-test@spinupmail.dev",
"localPart": "temp-signup-test",
"domain": "spinupmail.dev",
"meta": {
"allowedFromDomains": ["github.com", "example.com"],
"blockedSenderDomains": ["spam.test"],
"maxReceivedEmailCount": 25,
"maxReceivedEmailAction": "dropNew"
},
"integrations": [
{
"id": "sub_telegram_456",
"provider": "telegram",
"name": "Ops Alerts",
"eventType": "email.received"
}
],
"emailCount": 0,
"allowedFromDomains": ["github.com", "example.com"],
"blockedSenderDomains": ["spam.test"],
"inboundRatePolicy": null,
"maxReceivedEmailCount": 25,
"maxReceivedEmailAction": "dropNew",
"createdAt": "2026-03-08T09:00:00.000Z",
"createdAtMs": 1772960400000,
"expiresAt": "2026-03-08T11:00:00.000Z",
"expiresAtMs": 1772967600000
}Get an email address
/api/email-addresses/:idFetch a single inbox record for the current organization by address ID.
- 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 | Address identifier. |
Success response
| Field | Type | Details |
|---|---|---|
id | string | Spinupmail address identifier. |
address | string | Fully qualified inbox address in normalized lowercase form. |
localPart | string | Normalized inbox local part stored for the address. |
domain | string | Configured inbound domain assigned to the address. |
meta | unknown | Parsed address metadata. May be an object, string, or null depending on what was stored. |
integrations | Array<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }> | Active integration subscriptions currently attached to this inbox. |
emailCount | number | Number of stored emails currently linked to the inbox. |
allowedFromDomains | string[] | Normalized allowlist of sender domains extracted from metadata. |
blockedSenderDomains | string[] | Normalized denylist of sender domains extracted from metadata. |
inboundRatePolicy | object | null | Optional inbound abuse policy extracted from metadata. Null when no custom policy is set. |
maxReceivedEmailCount | number | null | Maximum number of emails this inbox may retain before the configured action applies. |
maxReceivedEmailAction | "cleanAll" | "dropNew" | null | Behavior applied when maxReceivedEmailCount is reached. dropNew accepts and discards additional mail without sender rejection. Returns null when no count limit is active. |
createdAt | string | null | When the address record was created. Constraints: ISO 8601 timestamp when present. |
createdAtMs | number | null | Millisecond representation of createdAt. Constraints: Unix timestamp in milliseconds when present. |
expiresAt | string | null | When the inbox expires if a TTL was configured. Constraints: ISO 8601 timestamp when present. |
expiresAtMs | number | null | Millisecond representation of expiresAt. Constraints: Unix timestamp in milliseconds when present. |
lastReceivedAt | string | null | When the inbox most recently received an email. Constraints: ISO 8601 timestamp when present. |
lastReceivedAtMs | number | null | Millisecond representation of lastReceivedAt. Constraints: Unix timestamp in milliseconds when present. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | x-org-id header is required for api key usage | An API key request omits X-Org-Id. |
400 | active organization is required | A session request has no active organization and does not pass X-Org-Id. |
401 | unauthorized | The request is not authenticated. |
403 | forbidden | The authenticated principal does not belong to the requested organization. |
404 | address not found | The requested address does not exist in the current organization. |
Example request
curl "https://api.spinupmail.com/api/email-addresses/addr_123" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123"Example response
{
"id": "addr_123",
"address": "signup-test@spinupmail.dev",
"localPart": "signup-test",
"domain": "spinupmail.dev",
"meta": {
"allowedFromDomains": ["github.com"],
"blockedSenderDomains": ["spam.test"]
},
"integrations": [
{
"id": "sub_telegram_456",
"provider": "telegram",
"name": "Ops Alerts",
"eventType": "email.received"
}
],
"emailCount": 4,
"allowedFromDomains": ["github.com"],
"blockedSenderDomains": ["spam.test"],
"inboundRatePolicy": null,
"maxReceivedEmailCount": null,
"maxReceivedEmailAction": null,
"createdAt": "2026-03-08T09:00:00.000Z",
"createdAtMs": 1772960400000,
"expiresAt": null,
"expiresAtMs": null,
"lastReceivedAt": "2026-03-08T09:45:10.000Z",
"lastReceivedAtMs": 1772963110000
}Update an email address
/api/email-addresses/:idUpdate an existing inbox, including renaming, TTL changes, metadata-backed policy fields, and integration subscriptions.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- Set ttlMinutes to null to remove the expiration time.
- Set maxReceivedEmailCount to null to clear the stored inbox-size override from metadata. Runtime enforcement then falls back to the current MAX_RECEIVED_EMAILS_PER_ADDRESS default.
- Organization-wide inbound retention is still limited by MAX_RECEIVED_EMAILS_PER_ORGANIZATION even though that value is not stored on each address record.
- If the Worker sets FORCED_MAIL_PREFIX, localPart updates are stored with <prefix>- prepended on the backend.
- When integrationSubscriptions is provided, existing email.received subscriptions are replaced with the new set.
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. |
Content-Type | application/json | Required | JSON request body is required. |
Path parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
id | string | Required | Address identifier to update. |
Request body
| Field | Type | Requirement | Details |
|---|---|---|---|
localPart | string | Optional | New local part for the address. Constraints: 1-30 characters after trim, letters/numbers/dot/underscore/plus/dash only. Reserved inbox keywords are rejected. If FORCED_MAIL_PREFIX is configured, the backend prepends <prefix>- before final validation and storage. |
ttlMinutes | number | null | Optional | New TTL in minutes. Null removes expiration. Omit to preserve the current expiration. Constraints: Whole number between 1 and 43200 when not null. |
meta | unknown | Optional | Replacement metadata base used for recomputing policy-backed fields when provided. |
domain | string | Optional | New configured domain for the address. |
integrationSubscriptions | Array<{ integrationId: string; eventType: "email.received" }> | Optional | Replacement integration subscriptions for email.received events. Constraints: Only organization admins can set this field. Each entry must reference an active integration in the same organization. |
allowedFromDomains | string[] | string | Optional | Replacement sender allowlist. Strings are split on commas and normalized. Constraints: Maximum 10 domains, each at most 50 characters. |
blockedSenderDomains | string[] | string | null | Optional | Replacement sender denylist. Null clears the current denylist. Constraints: Maximum 50 domains, each at most 50 characters. |
inboundRatePolicy | object | null | Optional | Replacement inbound abuse policy. Null clears the current policy. Constraints: When not null, must contain at least one supported positive whole-number field capped at 864000. |
maxReceivedEmailCount | number | null | Optional | Updated per-inbox retention override. Null removes the stored override. Constraints: Whole number between 1 and 100000 when not null. Effective runtime retention is still capped by the live /api/domains maxReceivedEmailsPerAddress value. |
maxReceivedEmailAction | "cleanAll" | "dropNew" | Optional | Replacement action for maxReceivedEmailCount. dropNew accepts and discards additional mail without sender rejection. Defaults to the existing action when omitted. |
Success response
| Field | Type | Details |
|---|---|---|
id | string | Spinupmail address identifier. |
address | string | Fully qualified inbox address in normalized lowercase form. |
localPart | string | Normalized inbox local part stored for the address. |
domain | string | Configured inbound domain assigned to the address. |
meta | unknown | Parsed address metadata. May be an object, string, or null depending on what was stored. |
integrations | Array<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }> | Active integration subscriptions currently attached to this inbox. |
emailCount | number | Number of stored emails currently linked to the inbox. |
allowedFromDomains | string[] | Normalized allowlist of sender domains extracted from metadata. |
blockedSenderDomains | string[] | Normalized denylist of sender domains extracted from metadata. |
inboundRatePolicy | object | null | Optional inbound abuse policy extracted from metadata. Null when no custom policy is set. |
maxReceivedEmailCount | number | null | Maximum number of emails this inbox may retain before the configured action applies. |
maxReceivedEmailAction | "cleanAll" | "dropNew" | null | Behavior applied when maxReceivedEmailCount is reached. dropNew accepts and discards additional mail without sender rejection. Returns null when no count limit is active. |
createdAt | string | null | When the address record was created. Constraints: ISO 8601 timestamp when present. |
createdAtMs | number | null | Millisecond representation of createdAt. Constraints: Unix timestamp in milliseconds when present. |
expiresAt | string | null | When the inbox expires if a TTL was configured. Constraints: ISO 8601 timestamp when present. |
expiresAtMs | number | null | Millisecond representation of expiresAt. Constraints: Unix timestamp in milliseconds when present. |
lastReceivedAt | string | null | When the inbox most recently received an email. Constraints: ISO 8601 timestamp when present. |
lastReceivedAtMs | number | null | Millisecond representation of lastReceivedAt. Constraints: Unix timestamp in milliseconds when present. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | invalid request body | The JSON body does not satisfy schema validation. |
400 | domain is invalid | domain is malformed or contains @. |
400 | domain is not allowed | domain is not one of the configured Worker domains. |
400 | integrationSubscriptions must reference active integrations in the current organization | integrationSubscriptions includes an unknown, archived, or out-of-organization integration. |
400 | allowedFromDomains contains invalid domain(s) | One or more sender allowlist entries are not valid domain hostnames. |
400 | blockedSenderDomains contains invalid domain(s) | One or more sender denylist entries are not valid domain hostnames. |
400 | inboundRatePolicy must be an object with at least one positive whole-number limit | inboundRatePolicy is present but does not resolve to a supported policy object. |
400 | localPart is reserved and cannot be used | localPart matches a reserved inbox keyword. |
403 | Only organization admins can manage integrations | integrationSubscriptions is provided by a non-admin member. |
404 | address not found | The requested address does not exist in the current organization. |
409 | Address already exists | The requested rename would collide with another existing address. |
Example request
curl -X PATCH "https://api.spinupmail.com/api/email-addresses/addr_123" \
-H "Content-Type: application/json" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
-d '{
"ttlMinutes": null,
"integrationSubscriptions": [
{
"integrationId": "int_telegram_123",
"eventType": "email.received"
}
],
"allowedFromDomains": ["github.com"],
"maxReceivedEmailCount": 50,
"maxReceivedEmailAction": "cleanAll"
}'Example response
{
"id": "addr_123",
"address": "temp-signup-test@spinupmail.dev",
"localPart": "temp-signup-test",
"domain": "spinupmail.dev",
"meta": {
"allowedFromDomains": ["github.com"],
"maxReceivedEmailCount": 50,
"maxReceivedEmailAction": "cleanAll"
},
"integrations": [
{
"id": "sub_telegram_456",
"provider": "telegram",
"name": "Ops Alerts",
"eventType": "email.received"
}
],
"emailCount": 4,
"allowedFromDomains": ["github.com"],
"blockedSenderDomains": [],
"inboundRatePolicy": null,
"maxReceivedEmailCount": 50,
"maxReceivedEmailAction": "cleanAll",
"createdAt": "2026-03-08T09:00:00.000Z",
"createdAtMs": 1772960400000,
"expiresAt": null,
"expiresAtMs": null,
"lastReceivedAt": "2026-03-08T09:45:10.000Z",
"lastReceivedAtMs": 1772963110000
}Delete an email address
/api/email-addresses/:idDelete an inbox and attempt to clean up associated raw email and attachment objects in R2.
- 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 | Address identifier to delete. |
Success response
| Field | Type | Details |
|---|---|---|
id | string | Deleted address identifier. |
address | string | Deleted inbox address. |
deleted | boolean | Always true on success. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
404 | address not found | The requested address does not exist in the current organization. |
500 | failed to clean up address files | The database record exists but R2 cleanup fails before deletion completes. |
Example request
curl -X DELETE "https://api.spinupmail.com/api/email-addresses/addr_123" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123"Example response
{
"id": "addr_123",
"address": "signup-test@spinupmail.dev",
"deleted": true
}
