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/domains returns maxReceivedEmailsPerAddress and maxReceivedEmailsPerOrganization so clients can mirror the active limits.
  • Address create and update requests still use maxReceivedEmailCount and maxReceivedEmailAction for the per-inbox policy stored in metadata.
  • The effective inbox retention limit is capped by the current maxReceivedEmailsPerAddress value, and inbound storage is also bounded by the organization-wide maxReceivedEmailsPerOrganization total.

Address endpoints also expose integration-linked fields:

  • integrationSubscriptions can be provided on create and update requests.
  • integrations is 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.

JSONJSON response example
{
  "localPart": "alerts",
  "integrationSubscriptions": [
    {
      "integrationId": "int_telegram_123",
      "eventType": "email.received"
    }
  ]
}

For provider setup, delivery behavior, and troubleshooting, see Integrations.

List email addresses

GET/api/email-addresses

List 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

Request headers
HeaderExampleRequirementDetails
Cookie session or X-API-KeyCookie: <better-auth session> or X-API-Key: spin_...RequiredAll documented product endpoints require an authenticated user session or a valid Better Auth API key.
X-Org-Idorg_abc123OptionalRequired for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead.

Query parameters

Query parameters
FieldTypeRequirementDetails
pagestringOptional

1-based page number.

Default: 1

pageSizestringOptional

Number of items per page.

Default: 10

Constraints: Clamped to 1-50.

searchstringOptional

Case-insensitive address search string applied to the organization inbox list.

sortBy"createdAt" | "address" | "lastReceivedAt"Optional

Sort field.

Default: createdAt

sortDirection"asc" | "desc"Optional

Sort direction.

Default: desc

Success response

Success response
FieldTypeDetails
itemsArray<object>

Page of address records.

items[].idstring

Spinupmail address identifier.

items[].addressstring

Fully qualified inbox address in normalized lowercase form.

items[].localPartstring

Normalized inbox local part stored for the address.

items[].domainstring

Configured inbound domain assigned to the address.

items[].metaunknown

Parsed address metadata. May be an object, string, or null depending on what was stored.

items[].integrationsArray<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }>

Active integration subscriptions currently attached to this inbox.

items[].emailCountnumber

Number of stored emails currently linked to the inbox.

items[].allowedFromDomainsstring[]

Normalized allowlist of sender domains extracted from metadata.

items[].blockedSenderDomainsstring[]

Normalized denylist of sender domains extracted from metadata.

items[].inboundRatePolicyobject | null

Optional inbound abuse policy extracted from metadata. Null when no custom policy is set.

items[].maxReceivedEmailCountnumber | 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[].createdAtstring | null

When the address record was created.

Constraints: ISO 8601 timestamp when present.

items[].createdAtMsnumber | null

Millisecond representation of createdAt.

Constraints: Unix timestamp in milliseconds when present.

items[].expiresAtstring | null

When the inbox expires if a TTL was configured.

Constraints: ISO 8601 timestamp when present.

items[].expiresAtMsnumber | null

Millisecond representation of expiresAt.

Constraints: Unix timestamp in milliseconds when present.

items[].lastReceivedAtstring | null

When the inbox most recently received an email.

Constraints: ISO 8601 timestamp when present.

items[].lastReceivedAtMsnumber | null

Millisecond representation of lastReceivedAt.

Constraints: Unix timestamp in milliseconds when present.

pagenumber

Current page number.

pageSizenumber

Resolved page size.

totalItemsnumber

Total addresses in the organization.

addressLimitnumber

Configured per-organization address cap from MAX_ADDRESSES_PER_ORGANIZATION.

totalPagesnumber

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

Common error responses
StatusErrorWhen it happens
400x-org-id header is required for api key usageAn API key request omits X-Org-Id.
400active organization is requiredA session request has no active organization and does not pass X-Org-Id.
401unauthorizedThe request is not authenticated.
403forbiddenThe authenticated principal does not belong to the requested organization.

Example request

BashRequest email addresses for an organization
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

JSONEmail addresses that belong to an organization
{
  "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

GET/api/email-addresses/recent-activity

Return 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

Request headers
HeaderExampleRequirementDetails
Cookie session or X-API-KeyCookie: <better-auth session> or X-API-Key: spin_...RequiredAll documented product endpoints require an authenticated user session or a valid Better Auth API key.
X-Org-Idorg_abc123OptionalRequired for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead.

Query parameters

Query parameters
FieldTypeRequirementDetails
limitstringOptional

Number of results to return.

Default: 10

Constraints: Clamped to 1-50.

cursorstringOptional

Opaque pagination cursor returned by the previous response.

searchstringOptional

Case-insensitive address search string applied before recent-activity ordering.

sortBy"recentActivity" | "createdAt"Optional

Sort field for the recent-activity feed.

Default: recentActivity

sortDirection"asc" | "desc"Optional

Sort direction for the recent-activity feed.

Default: desc

Success response

Success response
FieldTypeDetails
itemsArray<object>

Address records ordered by recent activity.

items[].idstring

Spinupmail address identifier.

items[].addressstring

Fully qualified inbox address in normalized lowercase form.

items[].localPartstring

Normalized inbox local part stored for the address.

items[].domainstring

Configured inbound domain assigned to the address.

items[].metaunknown

Parsed address metadata. May be an object, string, or null depending on what was stored.

items[].integrationsArray<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }>

Active integration subscriptions currently attached to this inbox.

items[].emailCountnumber

Number of stored emails currently linked to the inbox.

items[].allowedFromDomainsstring[]

Normalized allowlist of sender domains extracted from metadata.

items[].blockedSenderDomainsstring[]

Normalized denylist of sender domains extracted from metadata.

items[].inboundRatePolicyobject | null

Optional inbound abuse policy extracted from metadata. Null when no custom policy is set.

items[].maxReceivedEmailCountnumber | 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[].createdAtstring | null

When the address record was created.

Constraints: ISO 8601 timestamp when present.

items[].createdAtMsnumber | null

Millisecond representation of createdAt.

Constraints: Unix timestamp in milliseconds when present.

items[].expiresAtstring | null

When the inbox expires if a TTL was configured.

Constraints: ISO 8601 timestamp when present.

items[].expiresAtMsnumber | null

Millisecond representation of expiresAt.

Constraints: Unix timestamp in milliseconds when present.

items[].lastReceivedAtstring | null

When the inbox most recently received an email.

Constraints: ISO 8601 timestamp when present.

items[].lastReceivedAtMsnumber | null

Millisecond representation of lastReceivedAt.

Constraints: Unix timestamp in milliseconds when present.

nextCursorstring | null

Cursor to request the next page or null when exhausted.

totalItemsnumber

Total matching inbox count before cursor pagination is applied.

Common error responses

Common error responses
StatusErrorWhen it happens
400invalid cursorcursor is provided but cannot be decoded by the service.
400x-org-id header is required for api key usageAn API key request omits X-Org-Id.
400active organization is requiredA session request has no active organization and does not pass X-Org-Id.
401unauthorizedThe request is not authenticated.
403forbiddenThe authenticated principal does not belong to the requested organization.

Example request

BashRequest recent inbox activity
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

JSONRecently active inboxes for the organization
{
  "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

POST/api/email-addresses

Create 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

Request headers
HeaderExampleRequirementDetails
Cookie session or X-API-KeyCookie: <better-auth session> or X-API-Key: spin_...RequiredAll documented product endpoints require an authenticated user session or a valid Better Auth API key.
X-Org-Idorg_abc123OptionalRequired for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead.
Content-Typeapplication/jsonRequiredJSON request body is required.

Request body

Request body
FieldTypeRequirementDetails
localPartstringRequired

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.

ttlMinutesnumberOptional

Inbox lifetime in minutes.

Constraints: Whole number between 1 and 43200.

metaunknownOptional

Optional metadata. If you use allowedFromDomains or maxReceivedEmailCount, meta must resolve to an object or JSON object string.

domainstringOptional

Domain to assign. Defaults to the first configured Worker domain.

integrationSubscriptionsArray<{ 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.

allowedFromDomainsstring[] | stringOptional

Sender-domain allowlist. Strings are split on commas and normalized to lowercase unique domains.

Constraints: Maximum 10 domains, each at most 50 characters.

blockedSenderDomainsstring[] | stringOptional

Sender-domain denylist. Strings are split on commas and normalized to lowercase unique domains.

Constraints: Maximum 50 domains, each at most 50 characters.

inboundRatePolicyobjectOptional

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.

maxReceivedEmailCountnumberOptional

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: cleanAll

acceptedRiskNoticebooleanRequired

Explicit acknowledgement required by the API before creating an inbox.

Constraints: Must be true.

Success response

Success response
FieldTypeDetails
idstring

Spinupmail address identifier.

addressstring

Fully qualified inbox address in normalized lowercase form.

localPartstring

Normalized inbox local part stored for the address.

domainstring

Configured inbound domain assigned to the address.

metaunknown

Parsed address metadata. May be an object, string, or null depending on what was stored.

integrationsArray<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }>

Active integration subscriptions currently attached to this inbox.

emailCountnumber

Number of stored emails currently linked to the inbox.

allowedFromDomainsstring[]

Normalized allowlist of sender domains extracted from metadata.

blockedSenderDomainsstring[]

Normalized denylist of sender domains extracted from metadata.

inboundRatePolicyobject | null

Optional inbound abuse policy extracted from metadata. Null when no custom policy is set.

maxReceivedEmailCountnumber | 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.

createdAtstring | null

When the address record was created.

Constraints: ISO 8601 timestamp when present.

createdAtMsnumber | null

Millisecond representation of createdAt.

Constraints: Unix timestamp in milliseconds when present.

expiresAtstring | null

When the inbox expires if a TTL was configured.

Constraints: ISO 8601 timestamp when present.

expiresAtMsnumber | null

Millisecond representation of expiresAt.

Constraints: Unix timestamp in milliseconds when present.

Common error responses

Common error responses
StatusErrorWhen it happens
400invalid request bodyThe JSON body does not satisfy schema validation.
400acceptedRiskNotice must be trueacceptedRiskNotice is missing or false.
400EMAIL_DOMAINS is not configuredThe Worker has no configured domains.
400domain is invaliddomain is malformed or contains @.
400domain is not alloweddomain is not one of the configured Worker domains.
400integrationSubscriptions must reference active integrations in the current organizationintegrationSubscriptions includes an unknown, archived, or out-of-organization integration.
400allowedFromDomains contains invalid domain(s)One or more sender allowlist entries are not valid domain hostnames.
400blockedSenderDomains contains invalid domain(s)One or more sender denylist entries are not valid domain hostnames.
400inboundRatePolicy must be an object with at least one positive whole-number limitinboundRatePolicy is present but does not resolve to a supported policy object.
400localPart is required and may only contain letters, numbers, dot, underscore, plus, and dashlocalPart normalizes to an empty or invalid value.
400localPart is reserved and cannot be usedlocalPart matches a reserved inbox keyword.
403Only organization admins can manage integrationsintegrationSubscriptions is provided by a non-admin member.
409Address already existsAnother address already uses the same normalized address.
409Address limit reached. Each organization can create up to <limit> addresses.The organization has reached MAX_ADDRESSES_PER_ORGANIZATION.

Example request

BashRequest to create a new email address
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

JSONThe newly created email address
{
  "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

GET/api/email-addresses/:id

Fetch 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

Request headers
HeaderExampleRequirementDetails
Cookie session or X-API-KeyCookie: <better-auth session> or X-API-Key: spin_...RequiredAll documented product endpoints require an authenticated user session or a valid Better Auth API key.
X-Org-Idorg_abc123OptionalRequired for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead.

Path parameters

Path parameters
FieldTypeRequirementDetails
idstringRequired

Address identifier.

Success response

Success response
FieldTypeDetails
idstring

Spinupmail address identifier.

addressstring

Fully qualified inbox address in normalized lowercase form.

localPartstring

Normalized inbox local part stored for the address.

domainstring

Configured inbound domain assigned to the address.

metaunknown

Parsed address metadata. May be an object, string, or null depending on what was stored.

integrationsArray<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }>

Active integration subscriptions currently attached to this inbox.

emailCountnumber

Number of stored emails currently linked to the inbox.

allowedFromDomainsstring[]

Normalized allowlist of sender domains extracted from metadata.

blockedSenderDomainsstring[]

Normalized denylist of sender domains extracted from metadata.

inboundRatePolicyobject | null

Optional inbound abuse policy extracted from metadata. Null when no custom policy is set.

maxReceivedEmailCountnumber | 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.

createdAtstring | null

When the address record was created.

Constraints: ISO 8601 timestamp when present.

createdAtMsnumber | null

Millisecond representation of createdAt.

Constraints: Unix timestamp in milliseconds when present.

expiresAtstring | null

When the inbox expires if a TTL was configured.

Constraints: ISO 8601 timestamp when present.

expiresAtMsnumber | null

Millisecond representation of expiresAt.

Constraints: Unix timestamp in milliseconds when present.

lastReceivedAtstring | null

When the inbox most recently received an email.

Constraints: ISO 8601 timestamp when present.

lastReceivedAtMsnumber | null

Millisecond representation of lastReceivedAt.

Constraints: Unix timestamp in milliseconds when present.

Common error responses

Common error responses
StatusErrorWhen it happens
400x-org-id header is required for api key usageAn API key request omits X-Org-Id.
400active organization is requiredA session request has no active organization and does not pass X-Org-Id.
401unauthorizedThe request is not authenticated.
403forbiddenThe authenticated principal does not belong to the requested organization.
404address not foundThe requested address does not exist in the current organization.

Example request

BashRequest to GET /api/email-addresses/:id
curl "https://api.spinupmail.com/api/email-addresses/addr_123" \
  -H "X-API-Key: spin_..." \
  -H "X-Org-Id: org_abc123"

Example response

JSONResponse payload for GET /api/email-addresses/:id
{
  "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

PATCH/api/email-addresses/:id

Update 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

Request headers
HeaderExampleRequirementDetails
Cookie session or X-API-KeyCookie: <better-auth session> or X-API-Key: spin_...RequiredAll documented product endpoints require an authenticated user session or a valid Better Auth API key.
X-Org-Idorg_abc123OptionalRequired for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead.
Content-Typeapplication/jsonRequiredJSON request body is required.

Path parameters

Path parameters
FieldTypeRequirementDetails
idstringRequired

Address identifier to update.

Request body

Request body
FieldTypeRequirementDetails
localPartstringOptional

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.

ttlMinutesnumber | nullOptional

New TTL in minutes. Null removes expiration. Omit to preserve the current expiration.

Constraints: Whole number between 1 and 43200 when not null.

metaunknownOptional

Replacement metadata base used for recomputing policy-backed fields when provided.

domainstringOptional

New configured domain for the address.

integrationSubscriptionsArray<{ 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.

allowedFromDomainsstring[] | stringOptional

Replacement sender allowlist. Strings are split on commas and normalized.

Constraints: Maximum 10 domains, each at most 50 characters.

blockedSenderDomainsstring[] | string | nullOptional

Replacement sender denylist. Null clears the current denylist.

Constraints: Maximum 50 domains, each at most 50 characters.

inboundRatePolicyobject | nullOptional

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.

maxReceivedEmailCountnumber | nullOptional

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

Success response
FieldTypeDetails
idstring

Spinupmail address identifier.

addressstring

Fully qualified inbox address in normalized lowercase form.

localPartstring

Normalized inbox local part stored for the address.

domainstring

Configured inbound domain assigned to the address.

metaunknown

Parsed address metadata. May be an object, string, or null depending on what was stored.

integrationsArray<{ id: string; provider: "telegram"; name: string; eventType: "email.received" }>

Active integration subscriptions currently attached to this inbox.

emailCountnumber

Number of stored emails currently linked to the inbox.

allowedFromDomainsstring[]

Normalized allowlist of sender domains extracted from metadata.

blockedSenderDomainsstring[]

Normalized denylist of sender domains extracted from metadata.

inboundRatePolicyobject | null

Optional inbound abuse policy extracted from metadata. Null when no custom policy is set.

maxReceivedEmailCountnumber | 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.

createdAtstring | null

When the address record was created.

Constraints: ISO 8601 timestamp when present.

createdAtMsnumber | null

Millisecond representation of createdAt.

Constraints: Unix timestamp in milliseconds when present.

expiresAtstring | null

When the inbox expires if a TTL was configured.

Constraints: ISO 8601 timestamp when present.

expiresAtMsnumber | null

Millisecond representation of expiresAt.

Constraints: Unix timestamp in milliseconds when present.

lastReceivedAtstring | null

When the inbox most recently received an email.

Constraints: ISO 8601 timestamp when present.

lastReceivedAtMsnumber | null

Millisecond representation of lastReceivedAt.

Constraints: Unix timestamp in milliseconds when present.

Common error responses

Common error responses
StatusErrorWhen it happens
400invalid request bodyThe JSON body does not satisfy schema validation.
400domain is invaliddomain is malformed or contains @.
400domain is not alloweddomain is not one of the configured Worker domains.
400integrationSubscriptions must reference active integrations in the current organizationintegrationSubscriptions includes an unknown, archived, or out-of-organization integration.
400allowedFromDomains contains invalid domain(s)One or more sender allowlist entries are not valid domain hostnames.
400blockedSenderDomains contains invalid domain(s)One or more sender denylist entries are not valid domain hostnames.
400inboundRatePolicy must be an object with at least one positive whole-number limitinboundRatePolicy is present but does not resolve to a supported policy object.
400localPart is reserved and cannot be usedlocalPart matches a reserved inbox keyword.
403Only organization admins can manage integrationsintegrationSubscriptions is provided by a non-admin member.
404address not foundThe requested address does not exist in the current organization.
409Address already existsThe requested rename would collide with another existing address.

Example request

BashRequest to update an existing email address
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

JSONThe updated email address
{
  "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

DELETE/api/email-addresses/:id

Delete 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

Request headers
HeaderExampleRequirementDetails
Cookie session or X-API-KeyCookie: <better-auth session> or X-API-Key: spin_...RequiredAll documented product endpoints require an authenticated user session or a valid Better Auth API key.
X-Org-Idorg_abc123OptionalRequired for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead.

Path parameters

Path parameters
FieldTypeRequirementDetails
idstringRequired

Address identifier to delete.

Success response

Success response
FieldTypeDetails
idstring

Deleted address identifier.

addressstring

Deleted inbox address.

deletedboolean

Always true on success.

Common error responses

Common error responses
StatusErrorWhen it happens
404address not foundThe requested address does not exist in the current organization.
500failed to clean up address filesThe database record exists but R2 cleanup fails before deletion completes.

Example request

BashRequest to delete an email address
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

JSONDeleted email address confirmation
{
  "id": "addr_123",
  "address": "signup-test@spinupmail.dev",
  "deleted": true
}