Errors
One shape, two levels, and a request id on everything.
The envelope
type is a frozen set you can branch on and will never grow. code is specific and additive, so treat one you do not recognise as its type. requestId is on every response, successes included, and is what ties a report to a log line.
{ "error": { "type": "validation_error", "code": "invalid_email_address", "message": "Not a valid email address: ada@", "param": "to.0", "docUrl": "https://openemail.uk/docs/api/errors#invalid_email_address", "requestId": "req_e103790543af4…" }}param is dotted and indexed, so it points at the exact element (to.0, attachments.2.filename) rather than at the field that contains it.
docUrl links to the row for its code in Every error code, further down this page. When this page does not list a code yet, the link opens that table instead.
Status codes
| Status | type | Common codes |
|---|---|---|
| 400 | invalid_request_error | malformed_json, invalid_idempotency_key, step_up_code_invalid, step_up_code_expired, step_up_not_applicable |
| 401 | authentication_error | missing_api_key, invalid_api_key, revoked_api_key, invalid_credential_type, invalid_access_token, expired_access_token, grant_required, grant_expired, grant_workspace_gone, grant_access_lost |
| 403 | permission_error | insufficient_scope, from_address_forbidden, owner_only, api_key_only, beyond_caller_authority, grant_no_reach, mailbox_login_forbidden, step_up_required, account_suspended, workspace_suspended |
| 404 | not_found_error | resource_not_found |
| 409 | conflict_error | email_not_cancellable, translation_not_configured, label_name_taken, file_in_use, stream_paused, stream_locked |
| 412 | conflict_error | missing_chunks |
| 413 | invalid_request_error | upload_too_large |
| 422 | validation_error | invalid_email_address, reserved_header, too_many_recipients, unknown_parameter, idempotency_key_reuse, label_not_directly_settable, label_not_found, label_limit_reached, unknown_language, translation_too_long, workspace_limit_reached, invalid_follow_up, sender_category_limit_reached, ai_label_limit_reached |
| 429 | rate_limit_error | rate_limited, send_quota_exceeded, ai_quota_exceeded, upload_rate_limited, form_rate_limited, step_up_throttled, step_up_locked |
| 500 | api_error | internal_error |
| 502 | api_error | image_not_stored, upload_failed |
| 503 | api_error | translation_failed, step_up_undeliverable |
| 507 | api_error | upload_storage_full |
A 404 never distinguishes "does not exist" from "belongs to another workspace". That is deliberate: the difference is itself information.
Every 429 carries Retry-After, the seconds to wait before the same call can succeed. send_quota_exceeded is the monthly send allowance of the workspace's plan, counted for that workspace alone, and it resets on the first of the month, which is where its Retry-After points, so show it to a person rather than backing off. ai_quota_exceeded means that same workspace has used all of today's AI actions, and a send or a preview with translate spends one. Nothing was sent, and the allowance resets at midnight UTC, so a retry before then fails the same way.
Every 5xx has the type api_error, and they mean different things to a caller. A 503 is a dependency that did not answer, such as the translator or the mail service that delivers a verification code, and the request is worth retrying unchanged, except not_configured, which stays until disposable inboxes are set up. A 502 is a file or photo that could not be stored, also worth a retry, and a 507 is a workspace out of file storage. A 500 is ours and is worth reporting with its requestId.
Handling them
Branch on type for behaviour and read code for the message you show a human. An unrecognised code is not an error in your client. It means we named a failure more precisely than we used to.
const res = await fetch(`${BASE}/emails`, { method: 'POST', headers, body }); if (!res.ok) { const { error } = await res.json(); switch (error.type) { case 'rate_limit_error': throw new RateLimited(error.code); case 'validation_error': // error.param points at the offending field throw new BadRequest(`${error.param}: ${error.message}`); case 'authentication_error': // revoked_api_key and expired_api_key are worth telling an operator apart throw new AuthFailed(error.code); default: // quote requestId when you report it throw new Unexpected(error.message, error.requestId); }}Workspace limits
No plan caps how many webhooks, keys, addresses, templates, rules, roles or audiences a workspace has. Each one does carry a soft limit, a guard against a runaway script or a leaked key filling a workspace, and reaching it is a 422 workspace_limit_reached whose message names the limit. Nothing was created, so retrying the same call fails the same way until the workspace is below the limit again. Members are not limited at all.
| Resource | Soft limit |
|---|---|
| Webhook endpoints | 10 |
| Active API keys | 20 |
| Addresses | 1,000 |
| Pending invitations | 100 |
| Audiences | 100 |
| Templates | 200 |
| Rules | 100 |
| Custom roles | 24 |
These are soft limits, set per workspace rather than per plan. If you need more, write to [email protected] with the workspace ID and what you need, and we raise it within one working day.
Verification codes
Only an OAuth access token meets these. Before a sensitive change, such as deleting a domain or changing a webhook, the API asks the app for a verification code from the person it acts for, the same code the web app would ask them for. Authentication lists the operations and the two calls that verify a code.
| Code | Status | What to do |
|---|---|---|
| step_up_required | 403 | Ask for a code with POST /security/step-up, verify it with POST /security/step-up/verify, then repeat the call once. Or ask the person to choose Allow changes for 60 minutes on the app in Account → Connected apps. Nothing was changed. |
| step_up_code_invalid | 400 | The code did not match. The message says how many tries are left, the fewest of those left on this code, the wrong codes left before the app is paused and the wrong codes left before all of the person’s apps are paused, so ask the person again. |
| step_up_code_expired | 400 | The code ran out after 10 minutes, or none was waiting. Ask for a new one with POST /security/step-up. |
| step_up_locked | 429 | Five wrong tries at the same code: a plain POST /security/step-up starts a fresh one. Or the app has entered 10 wrong codes in 24 hours, or all of the person’s apps together 20: the message says verification is paused until a given time, and no code can be asked for or checked before then. Removing an app does not lift the second pause, and codes entered on the website still work. |
| step_up_throttled | 429 | The app has asked for 5 codes in the last hour, or 20 in the last 24 hours. The message says when to try again. |
| step_up_undeliverable | 503 | The email carrying the code could not be sent, and no code is waiting. Try again in a moment. |
| step_up_not_applicable | 400 | An API key called a verification endpoint. Keys are never asked for a code, so there is nothing to verify. |
Every error code
Every code the API sends, in alphabetical order, with the statuses and types it comes with. The docUrl of an error links to the row for its code.
| Code | Status | type | Meaning |
|---|---|---|---|
| account_exists | 409 | conflict_error | An account on Nextmail already signs in as that address, so the address cannot get a password of its own. Invite that account to the workspace instead. |
| account_suspended | 403 | permission_error | The account behind the key or app is suspended, so nothing it signs in to answers until the suspension is lifted. Write to [email protected] to ask why or to appeal. |
| add_on_already_active | 409 | conflict_error | The workspace already has this add-on, so there is nothing to buy. GET /billing/add-ons shows its state. |
| add_on_not_active | 409 | conflict_error | The workspace does not have this add-on, or it is already ending, so there is nothing to cancel. |
| add_on_not_eligible | 409 | conflict_error | The workspace does not qualify for this add-on yet. A dedicated IP needs the Business or Enterprise plan and at least 100,000 emails sent in a month, because below that volume it delivers worse than the shared IPs. eligibility on GET /billing/add-ons says which of the two is missing. |
| add_on_sold_out | 409 | conflict_error | Every dedicated IP is taken at the moment. Contact support and one is leased for the workspace. |
| add_on_unavailable | 503 | api_error | Add-ons are not on sale right now, or billing did not answer, so nothing was bought or changed. |
| address_disabled | 409 | conflict_error | The address is switched off, so nothing would reach a forwarding destination. Turn the address on first. |
| address_has_no_mailbox | 422 | validation_error | The address is switched off or forwards all its mail, so a mail app has no mailbox to open. |
| address_not_allowed | 403 | permission_error | The caller may not use that address here, such as importing mail into it or publishing an encryption key for it, so choose an address it holds. |
| address_not_found | 404 | not_found_error | The address to import into is not in this workspace. |
| address_required | 422 | validation_error | These settings are kept per address, so pass ?address= with the address, or *@domain for a catch-all. |
| address_reserved | 409 | conflict_error | The address is reserved for an account, so it cannot be added to the domain. |
| address_unavailable | 409 | conflict_error | The address cannot have a password: it has to be an enabled address on a domain of this workspace. |
| ai_label_limit_reached | 422 | validation_error | The workspace already has as many labels that AI applies as it may, so turn one off to turn another on. |
| ai_not_configured | 409 | conflict_error | AI writing is not available on this server, so nothing was written. |
| ai_quota_exceeded | 429 | rate_limit_error | The workspace has used all of the AI actions its plan includes today. A send or preview with translate spends one, and so does writing with AI, so nothing was sent or written and a retry works after midnight UTC. |
| already_enrolled | 409 | conflict_error | The contact is in the automation now, or finished it too recently to come back in under its re-entry setting. |
| already_running | 409 | conflict_error | An import into that address, an import from another provider into this workspace or an export of this workspace is already under way, so wait for it to finish, or cancel the import. |
| api_key_only | 403 | permission_error | This call needs an API key and was made with an OAuth access token, so make it with a key. |
| app_host_in_use | 409 | conflict_error | Another workspace already uses that web app address, or a mail domain, a tracking host or a files host has that name, so choose another subdomain. |
| app_host_unavailable | 503 | api_error | The web app address could not be set up or removed just now, so try again in a minute. |
| app_password_limit_reached | 422 | validation_error | The person already holds as many app passwords for this address as it allows. Revoke one first. |
| attachment_unreachable | 422 | validation_error | A file named by url could not be fetched: the address is not https, points at a private network, did not answer within 15 seconds, answered with an error, or is larger than 25 MB. |
| audience_immutable | 409, 422 | conflict_error, validation_error | The default audience cannot be renamed, deleted or have its members changed by hand. |
| audience_is_everyone | 422 | validation_error | One of the audiences is All contacts, which holds everyone this workspace has ever written to, so a broadcast cannot go to it. Send to an audience people joined. |
| audience_member_not_found | 404, 422 | not_found_error, validation_error | The contact is not in that audience. |
| audience_not_found | 404 | not_found_error | No audience in this workspace has that id. |
| automation_archived | 409 | conflict_error | The automation is archived, so it can no longer be changed or turned on. Duplicate it to start again from its steps. |
| automation_enrollment_finished | 409 | conflict_error | The contact already completed or left the automation, so there is nothing to take them out of. |
| automation_enrollment_not_found | 404 | not_found_error | No enrollment in that automation has that id. |
| automation_limit_reached | 403 | permission_error | The plan of the workspace covers no more live automations, as the message says, so pause one or upgrade before turning this one on. |
| automation_not_found | 404 | not_found_error | No automation you can reach has that id. Restoring a version answers this, with param set to version, when the automation has no such version. |
| automation_not_live | 409 | conflict_error | The automation is a draft, paused or archived, so nobody can be enrolled in it until it is live. |
| automation_not_published | 409 | conflict_error | The automation was never published, so publish it before you pause or resume it. |
| automation_sender_refused | 422 | validation_error | The caller may not send mail for this automation, so publish, resume or test it with a credential that holds emails:send. |
| bad_chunk | 400 | invalid_request_error | An uploaded part does not match the import file it belongs to, so send that part again. |
| beyond_caller_authority | 403 | permission_error | A key can only get scopes, a role, a mode, an expiry and send addresses within what the caller holds, and this request asks for more, as the message says. |
| billing_address_rejected | 422 | validation_error | The billing provider refused the address, so check it. In the US and Canada the state is its two-letter code. |
| billing_details_required | 409 | conflict_error | The charge has no billing name and address yet, so add them with PUT /billing/invoices/{orderId}/details before asking for its invoice. |
| billing_not_configured | 503 | api_error | Billing, paid plans or pay as you go are unavailable right now, as the message says, so there is nothing to buy or change. |
| billing_unreachable | 502 | api_error | The billing provider did not answer, so nothing changed. Try again in a moment. |
| blocklist_entry_too_broad | 422 | validation_error | A blocklist entry needs at least two letters or numbers, or it would match far too much mail. |
| booking_ended | 409 | conflict_error | The booking was already declined or cancelled, or the meeting has started, so it can no longer be changed. |
| booking_link_taken | 409 | conflict_error | Another booking page already uses that slug. Links are unique across Nextmail, so pick another one. |
| booking_not_found | 404 | not_found_error | No booking of the person the key or app acts for has that id. |
| booking_not_pending | 409 | conflict_error | The booking is not waiting for approval: it was already approved, declined or cancelled, or its time has passed. |
| booking_page_limit_reached | 403 | permission_error | The person already has as many booking pages as one person may hold. Delete one first. |
| booking_page_not_found | 404 | not_found_error | No booking page of the person the key or app acts for has that id. |
| booking_time_unavailable | 409 | conflict_error | The new time is not open on the booking page: it is taken, outside the hours, too soon or too far ahead. List the open times with bookingId and pick one. |
| bot_check_failed | 403 | permission_error | The sign-up carried a bot check token, in cf-turnstile-response, oe_bot_check or the X-Bot-Check header, and the check refused it. Nothing was stored. Complete the check again and send the new token. |
| branding_unavailable | 409 | conflict_error | The workspace is a personal space, which carries no brand, so its brand images, fonts and sign-in background cannot be set. Use a key from a business workspace. |
| broadcast_not_cancellable | 409 | conflict_error | Every message in the broadcast has gone out, or it is past the point where it can be cancelled. |
| broadcast_not_draft | 409 | conflict_error | The broadcast is not a draft, so it cannot be sent this way: it has already been sent or scheduled. |
| broadcast_not_editable | 409 | conflict_error | Only a draft, or a scheduled broadcast that has not started going out, can be changed. |
| broadcast_not_found | 404 | not_found_error | No broadcast has that id, or it was sent from an address or domain the key does not hold. |
| calendar_feed_limit | 409 | conflict_error | You already have as many calendar links as one person may keep. Delete one before you create another. |
| calendar_feed_not_found | 404 | not_found_error | No calendar link of yours has that id. |
| calendar_subscription_exists | 409 | conflict_error | You already subscribe to a calendar at that address. |
| calendar_subscription_limit | 409 | conflict_error | You already subscribe to as many calendars as one person may. Remove one before you add another. |
| calendar_subscription_not_found | 404 | not_found_error | No calendar subscription of yours has that id. |
| calendar_subscription_refresh_too_soon | 429 | rate_limit_error | The calendar was refreshed moments ago. Wait a minute before you refresh it again. |
| calendar_subscription_unreadable | 422 | validation_error | The address did not answer with a calendar: it was unreachable, private, too large, or not an iCalendar (.ics) file. |
| calendar_subscription_url_invalid | 422 | validation_error | The calendar address must be a public https, http or webcal link without a user name or password. |
| capability_unsupported | 422 | validation_error | The key or app is limited to some addresses or domains, and this call reaches mail or settings beyond them, so use a key without that limit. |
| chunk_too_large | 400 | invalid_request_error | An import upload part is larger than the part size, so send the file in parts of the size the message gives. |
| connected_mailbox_already_connected | 409 | conflict_error | That mailbox is already connected to this workspace, so there is nothing to connect again. Send a new password with PATCH if it stopped working. |
| connected_mailbox_connected_elsewhere | 409 | conflict_error | Another workspace has that mailbox connected, and a mailbox syncs into one workspace at a time. Disconnect it there first. |
| connected_mailbox_domain_hosted | 400 | invalid_request_error | Mail for that address already comes to Nextmail, because its domain is verified here or belongs to Nextmail, so it needs no connecting. |
| connected_mailbox_no_password | 400 | invalid_request_error | The mailbox signs in with Microsoft, so it has no password to change. Sign in to Microsoft again from the app when it needs signing in. |
| connected_mailbox_not_allowed | 403 | permission_error | Connecting, changing and disconnecting mailboxes needs the domains:write permission, which the caller lacks. |
| connected_mailbox_not_found | 404 | not_found_error | No connected mailbox has that id in this workspace, or the caller may not see its address. |
| connected_mailbox_send_server_unknown | 400 | invalid_request_error | No outgoing mail server answered for the address, so mail could not be sent through it. Pass smtpHost, and smtpPort if it is not 465 or 587. |
| connected_mailbox_send_sign_in_failed | 400 | invalid_request_error | The outgoing mail server did not accept the password, even though the incoming one did. Check that the app password allows sending. |
| connected_mailbox_wrong_account | 400 | invalid_request_error | The Microsoft sign-in was for a different account than the mailbox being signed in again. Sign in as the address of that mailbox. |
| connection_not_found | 422 | validation_error | The mailbox the message would be sent from no longer exists. |
| connector_action_failed | 422 | validation_error | The connected app refused the action, most often because the input does not match what the action takes. The message carries what the app said. |
| connector_action_not_found | 404 | not_found_error | The app behind the connector has no action with that name. List the ones it has with the actions endpoint. |
| connector_app_not_found | 404 | not_found_error | The connector catalogue has no app with that slug. |
| connector_app_unavailable | 422 | validation_error | The app is in the catalogue but cannot be connected yet. Its connectable field says so before you try. |
| connector_limit_reached | 403 | permission_error | The plan holds no more connectors for this account or workspace. Disconnect one, or move to a larger plan. |
| connector_not_active | 409 | conflict_error | The connector is paused, still waiting for its sign-in, or needs reconnecting, so nothing can be run through it. |
| connector_not_found | 404 | not_found_error | No connector the key or app can reach has that id. |
| connector_provider_failed | 503 | api_error | The connection service did not answer in time. Connecting, reading and listing changed nothing and can be sent again. A run may or may not have reached the app, so look before running it again. |
| connector_read_only | 403 | permission_error | The connector is set to read only, so an action that changes something in the app is refused. Set its access to full first. |
| connectors_unavailable | 503 | api_error | Connectors are not switched on for this service yet, so no app can be connected or used. |
| contact_card_email_taken | 409 | conflict_error | That email address already belongs to another contact card, so change that card instead. |
| contact_card_limit | 409 | conflict_error | The workspace already keeps as many contact cards as it may. Delete some before you add more. |
| contact_card_not_found | 404 | not_found_error | No contact card you can see has that id. |
| contact_card_uid_taken | 409 | conflict_error | Another contact card already has that vCard UID. |
| contact_exists | 409 | conflict_error | That email address is already a contact, so change the existing contact instead. |
| contact_not_found | 404, 422 | not_found_error, validation_error | No contact has that email address, so add the contact to the book first. |
| contact_unreachable | 409 | conflict_error | The contact cannot be enrolled because the address is on the suppression list or has unsubscribed from the audience the automation sends through. |
| deep_check_limit_reached | 409 | conflict_error | The deep check would pass the monthly pay as you go limit of the workspace, so no address was checked and nothing was billed. Raise the limit, or run the check without deep. |
| deep_check_unavailable | 409, 503 | conflict_error, api_error | A deep check is billed per address through pay as you go, which is off, paused or not part of the plan, as the message says. With 503 the mailbox check itself did not answer. Nothing was billed. Run the check without deep, which is free. |
| delivery_not_replayable | 409 | conflict_error | The webhook delivery has no stored event to send again. |
| dns_busy | 409 | conflict_error | A DNS sync is running on the domain right now, so nothing was changed. Try again in a moment. |
| dns_connection_in_use | 409 | conflict_error | Domains are still attached to the DNS connection, or records written through it are still published, so it cannot be removed yet. |
| dns_connection_unusable | 409 | conflict_error | The DNS connection was disconnected, or the provider no longer accepts it, so connect the account again in the app. |
| dns_provider_error | 502 | api_error | The DNS provider refused or failed the request, so try again in a moment, and connect the account again if it keeps failing. |
| dns_unavailable | 503 | api_error | Setting up DNS through a provider is not available on this server right now. |
| dns_zone_conflict | 409 | conflict_error | The domain is already set up through another zone, or still has records written in one, so disconnect the connection that holds that zone first. |
| dns_zone_unusable | 409 | conflict_error | The zone cannot take records through this connection: it is not active at the DNS provider, the connection may not write DNS, or a test record could not be written. |
| domain_allowance_reached | 403 | permission_error | The plan of the workspace includes no more domains, so upgrade it or remove a domain first. |
| domain_already_added | 409 | conflict_error | The workspace already has this domain. |
| domain_claimed | 409 | conflict_error | Another workspace has already claimed this domain. |
| domain_holds_reserved_addresses | 409 | conflict_error | The domain holds reserved account addresses that removing it would delete, so it cannot be removed here. |
| domain_moving | 409 | conflict_error | The domain is being moved to another workspace, so try again once the move has finished. |
| domain_not_sendable | 409 | conflict_error | The domain cannot send yet because its signing records are not in place, so add the records shown for the domain and try again. A DMARC policy stricter than none waits for them too. |
| domain_not_verified | 409 | conflict_error | The domain has to be verified before a tracking host, a files host, a web app address, a logo or a mark certificate can be set on it, or an encryption key published for one of its addresses. |
| draft_body_unavailable | 422 | validation_error | The draft has no body to send, so check that the message is there and send again. |
| email_not_cancellable | 409 | conflict_error | The message is no longer queued, so it can no longer be cancelled, rescheduled or edited. |
| empty_file | 400 | invalid_request_error | One of the files chosen for the import is empty. |
| encryption_locked | 422 | validation_error | The message was encrypted before it was scheduled, so its wording and recipients cannot be edited, and it has to be cancelled and sent again. |
| event_cancelled | 409 | conflict_error | The meeting has been cancelled, so it can no longer be answered or changed. |
| event_not_found | 404 | not_found_error | No calendar event has that id. |
| event_not_subscribed | 409 | conflict_error | The webhook no longer listens for this event, so the delivery is not sent again. |
| event_out_of_scope | 409 | conflict_error | The webhook no longer covers the address this event is about, so the delivery is not sent again. |
| event_rate_limited | 429 | rate_limit_error | The key or app sent more contact events in a minute than it may, and a batch counts each event it holds, so send the rest a minute later. |
| event_read_only | 403 | permission_error | The event comes from a calendar you subscribe to, so it can only be changed where that calendar is published. |
| expired | 409 | conflict_error | The API key has expired, so it cannot be rotated and a new one has to be created, or the export is more than a year old and was deleted, so start a new one. |
| expired_access_token | 401 | authentication_error | The OAuth access token has expired, so renew it with its refresh token or connect the app again. |
| expired_api_key | 401 | authentication_error | The API key has passed its expiry date, so create a new one in the console. |
| export_limit_reached | 429 | rate_limit_error | The workspace was exported in the last day, and it can be exported once a day, so try again when the last export is a day old. |
| export_not_permitted | 403 | permission_error | The caller may not export this workspace: only its owner, or a member whose role holds every permission, may, and a workspace that requires two-factor sign-in needs it on that account. The message says which. |
| export_not_ready | 409 | conflict_error | The export is still being made, or failed, so there is no archive to download yet. |
| extension_limit | 422 | validation_error | All 23 extensions of the disposable inbox are spent, so it cannot be extended again. |
| file_in_use | 409 | conflict_error | The file is kept because a message received it, sent it, links to it or will send it, as the message says, so it cannot be deleted. |
| file_too_large | 400 | invalid_request_error | One of the files chosen for the import is larger than 100 GB. |
| file_unshareable | 422 | validation_error | That type of file can run code on the computer that opens it, so it cannot get a public link. |
| first_workspace | 409 | conflict_error | The first workspace an account made cannot be deleted, so delete the whole account in the app instead. |
| form_closed | 409 | conflict_error | The form is paused, so it takes no sign-ups until it is resumed. |
| form_not_found | 404 | not_found_error | No form you can reach has that id. The public sign-up endpoint also answers this for a form that was never published. |
| form_not_published | 409 | conflict_error | The form was never published, so publish it before you pause or resume it. |
| form_rate_limited | 429 | rate_limit_error | Too many sign-ups came from the same network in the last few minutes, so try again later. |
| form_sender_refused | 422 | validation_error | Confirmation emails cannot go out from that address, so choose a sending address of this workspace that you may send as. |
| form_site_not_allowed | 403 | permission_error | The form lists the sites that may use it, and the sign-up came from another site or did not say where it came from. Post from a listed site, or from a server send the page address as oe_source. |
| form_starter_not_found | 404 | not_found_error | No form starter has that slug. List them with GET /forms/starters. |
| form_submission_not_found | 404 | not_found_error | The form has no submission with that id. |
| forward_required | 409 | conflict_error | The address has no forwarding destination that is on, so it cannot stop keeping a copy in the mailbox. Add one first. |
| from_address_forbidden | 403 | permission_error | The role, key or app may not send as the From address, so send from an address it holds. |
| grant_access_lost | 401 | authentication_error | The person who approved the app no longer has access to its workspace, so connect the app again and choose another. The same code comes back while the workspace requires two-factor authentication and that person has not turned it on, and then the message says so. |
| grant_expired | 401 | authentication_error | The access given to the app has run out, so connect it again to renew it. |
| grant_no_reach | 403 | permission_error | The person who connected the app can no longer reach any address it was approved for, so connect it again to choose what it may reach. |
| grant_required | 401 | authentication_error | The app has not been approved under the current access rules, so connect it again to choose what it may reach. |
| grant_workspace_gone | 401 | authentication_error | The workspace the app was approved for no longer exists, so connect the app again and choose another. |
| idempotency_key_reuse | 422 | validation_error | The Idempotency-Key was already used for a different request, so send a new key with a new request. |
| image_busy | 503 | api_error | The image could not be processed right now, so upload it again in a moment. |
| image_not_stored | 502 | api_error | The photo was processed but could not be saved, so upload it again. |
| inactive_api_key | 401 | authentication_error | The API key is switched off, so turn it back on in the console or use another key. |
| inbox_expired | 401 | authentication_error | The disposable inbox has expired, so create a new one. |
| incomplete_order | 422 | validation_error | A rule reorder has to name every rule on the mailbox exactly once. |
| insufficient_authority | 403 | permission_error | The caller lacks the authority for this, and the message says why: a role or invitation it hands out or sends again holds a permission the caller lacks or reaches addresses or domains the caller does not, or the member an access token acts for lacks a console-only permission the call needs, such as workspace:manage. Leave out what the message names, or ask the owner. |
| insufficient_scope | 403 | permission_error | The key or token lacks the scope this call needs, and the message names it. For an access token that is a permission the app was not given, or one the role of the member it acts for no longer holds, so connect the app again once the role holds it. |
| internal_error | 500 | api_error | Something failed on our side, so report it with the requestId. |
| invalid_access_token | 401 | authentication_error | The access token belongs to an app that is no longer registered, or to an account that no longer exists, so connect the app again. |
| invalid_address | 422 | validation_error | That name cannot be used in a disposable email address. |
| invalid_api_key | 401 | authentication_error | The Authorization header does not carry a valid API key, so check the key and send it as a Bearer token. |
| invalid_app_host | 422 | validation_error | The web app address cannot be used: it is not a hostname, it is a bare domain, or it is on a domain Nextmail runs. The message suggests one that works. |
| invalid_audience | 422 | validation_error | The audience has no name or could not be created, or the call names more audiences than one call may. |
| invalid_automation | 422 | validation_error | The automation breaks a rule, and the message names the first one. When the trigger, the steps or the settings are at fault, problems in the error lists every problem that blocks it, each with its code, path, stepKey and message. A test send answers this when the step is not an email or its template cannot be sent. |
| invalid_booking_page | 422 | validation_error | The booking page is not valid, as the message says: the link has characters it may not have or is reserved, the sender is not an address the person may send from, a video meeting has no link, or a question is malformed. |
| invalid_contact | 422 | validation_error | The contact has no email address or could not be saved, it names more audiences than one call may, or a custom field breaks a rule: a key that is not a letter followed by up to 63 letters, digits or underscores, a value over 500 characters, or a 51st field. For a custom field param is attributes.<key>, or attributes when the contact would hold too many. |
| invalid_contact_card | 422 | validation_error | The contact card has nothing to keep, a field is not valid (a birthday is YYYY-MM-DD or --MM-DD), or it tries to change the address a saved contact is known by. |
| invalid_credential_type | 401 | authentication_error | The request sent a session cookie, or a token that is neither an API key nor an access token Nextmail issued, so send an API key or access token as a Bearer token. |
| invalid_cursor | 400, 422 | invalid_request_error, validation_error | The cursor does not come from this list, so start without one or pass the nextCursor of the previous page. |
| invalid_email_address | 422 | validation_error | A recipient or address field is not a valid email address, and param points at it. |
| invalid_event | 422 | validation_error | The contact event breaks a rule, and param points at the field: a name that is not 1 to 100 letters, digits, dots, colons, dashes and underscores, no contact named, properties over 50 keys or 4 KB, or an occurredAt in the future or more than 90 days ago. |
| invalid_follow_up | 422 | validation_error | The reminder time has to be in the future, after the message goes out and at most 365 days out. |
| invalid_form | 422 | validation_error | The form document or settings break a rule, and the message names the field and the rule. From a design with AI it means the designer could not make a valid form, so nothing was created or changed. |
| invalid_form_submission | 422 | validation_error | An answer is missing or not valid, and fields lists each field key with the reason. |
| invalid_idempotency_key | 400 | invalid_request_error | The Idempotency-Key header has to be 1 to 255 letters, digits, underscores, dots, colons or hyphens. |
| invalid_image | 422 | validation_error | The upload is not an image that can be used as a contact or address photo or a brand image, and the message says why. |
| invalid_key | 400 | invalid_request_error | The provider did not accept the API key given for the import. |
| invalid_knowledge_body | 422 | validation_error | A note needs text of at most 20,000 characters, so send a body within that length. |
| invalid_knowledge_connector | 422 | validation_error | The connector cannot be read: its url is not a public https address, or the page there is not a site, sitemap, feed or Zendesk help center of the kind given, so check both. |
| invalid_knowledge_refresh | 422 | validation_error | A link or a connector is read again every 1, 7 or 30 days, so send one of those in refreshDays, or null to never read it again. |
| invalid_knowledge_scope | 422 | validation_error | The scope must be empty for the whole workspace, @ and one of the workspace's domains, or one of its addresses, so pick one of those. |
| invalid_knowledge_title | 422 | validation_error | A knowledge item needs a title of at most 200 characters, so send a shorter or non-empty title. |
| invalid_knowledge_url | 422 | validation_error | The link is not a public http or https address on the default port, or it points at a private host or at the Nextmail API, so send a public page. |
| invalid_parameter | 400, 422 | invalid_request_error, validation_error | A query or body value is not valid, and param names it while the message says why. |
| invalid_rule | 422 | validation_error | The conditions or actions of the rule are not valid, and the message says which. |
| invalid_schedule | 422 | validation_error | scheduledAt has to be in the future and at most 365 days out. |
| invalid_storage_host | 422 | validation_error | The files host is not a host this domain can use, and the message says why. |
| invalid_task | 422 | validation_error | The task is not valid, as the message says: a schedule trigger needs a schedule, a time zone has to be one the platform knows, and a toolkit has to be one a task may use. |
| invalid_template | 422 | validation_error | The template is not valid, for example a placeholder is not declared or the html is missing or too long. |
| invalid_template_prop | 422 | validation_error | A template prop has to be a string, a number or a boolean. |
| invalid_time | 400 | invalid_request_error | The start or end of the event is not a valid time, it ends before it starts, or it lasts more than two years. |
| invalid_tracking_host | 422 | validation_error | The tracking host is not a host this domain can use, and the message says why. |
| invalid_webhook_url | 422 | validation_error | The webhook URL cannot receive deliveries, and the message says why. |
| invitation_accepted | 409 | conflict_error | The invitation has already been accepted, so remove the member instead. |
| invitation_expired | 422 | validation_error | The invitation has expired, so ask whoever sent it for a new one. |
| invitation_limit_reached | 409 | conflict_error | The workspace, or the person inviting, has sent as many invitations as a day allows, so try again tomorrow. |
| invitation_not_found | 404 | not_found_error | The invitation does not exist or is no longer outstanding. |
| invitation_revoked | 422 | validation_error | The invitation was withdrawn or answered before it could be accepted. |
| invitation_too_soon | 409 | conflict_error | That person was invited in the last ten minutes, so wait before inviting them again. |
| invitation_wrong_account | 422 | validation_error | The invitation was sent to another email address than the account accepting it. |
| invoice_not_ready | 504 | api_error | The invoice is still being written, so ask for it again in a moment. |
| invoice_unavailable | 409 | conflict_error | A draft or void charge has no invoice. |
| ip_not_allowed | 403 | permission_error | The API key has an IP allowlist and the request came from an address outside it, so send it from one of those addresses or change the list. |
| item_import_empty | 400 | invalid_request_error | The file sent for the calendar or contacts import is empty. |
| item_import_not_cancellable | 409 | conflict_error | The calendar or contacts import has already finished, so there is nothing to cancel. |
| item_import_not_found | 404 | not_found_error | The calendar or contacts import does not exist. |
| item_import_not_undoable | 409 | conflict_error | The calendar or contacts import is still running, or what it added was already removed. |
| item_import_not_uploading | 409 | conflict_error | The calendar or contacts import already has its file. |
| item_import_too_large | 413 | invalid_request_error | The file is too large, since a calendar file may be 10 MB and a contacts file 20 MB. |
| key_already_published | 409 | conflict_error | The address already has a published key, or that key was published for it before, so rotate it with replaces or generate a new key. |
| knowledge_ai_unavailable | 503 | api_error | The AI did not answer while it drafted the note, so try again in a moment. |
| knowledge_allowance_reached | 409 | conflict_error | The workspace already holds as many knowledge items, or as much text, as its plan allows, so remove items or move to a larger plan. |
| knowledge_answer_required | 422 | validation_error | An unanswered question becomes a note only with its answer, so send the answer in body. |
| knowledge_connector_exists | 409 | conflict_error | A connector for the same address already exists at that level, so change that connector instead of adding it again. |
| knowledge_connector_not_found | 404 | not_found_error | No connector with that id is in this workspace, or the caller cannot reach the level it belongs to. |
| knowledge_file_empty | 422 | validation_error | The uploaded file has no bytes, so send the file itself as the request body. |
| knowledge_file_too_large | 413 | invalid_request_error | The file is larger than a knowledge item can be: 20 MB for a document, 10 MB for an image. |
| knowledge_file_unsupported | 422 | validation_error | That kind of file cannot be read into the knowledge base, so send a PDF, Word, Excel, OpenDocument, CSV, HTML, XML, Markdown, text or image file. |
| knowledge_flag_not_found | 404 | not_found_error | No flag with that id is in this workspace, or the caller cannot reach both of the items it names. |
| knowledge_link_exists | 409 | conflict_error | The same link is already in the knowledge base at that level, so change that item instead of adding it again. |
| knowledge_not_a_link | 422 | validation_error | Only a link has an address to change, so leave url out for notes and files. |
| knowledge_not_a_note | 422 | validation_error | Only a note has a body you can edit or pin, so leave body and pinned out for files and links. |
| knowledge_not_found | 404 | not_found_error | No knowledge item with that id is in this workspace, or the caller cannot reach the level it belongs to. |
| knowledge_nothing_to_save | 422 | validation_error | The conversation has no messages yet, or holds no facts worth keeping, so there is no note to draft from it. |
| knowledge_scope_not_reached | 403 | permission_error | The caller cannot change knowledge at that level: the whole workspace needs every address, a domain needs the whole domain, and an address needs that address. |
| knowledge_store_unavailable | 503 | api_error | The knowledge index did not answer in time. Nothing was lost, so try again in a moment. |
| knowledge_suggestion_decided | 409 | conflict_error | The suggestion or question was already accepted or dismissed, so list the pending ones again. |
| knowledge_suggestion_not_found | 404 | not_found_error | No suggestion or question with that id is in this workspace, or the caller cannot reach the level it belongs to. |
| knowledge_thread_not_found | 404 | not_found_error | No conversation with that id is in this workspace, or the caller cannot read the address it belongs to. |
| knowledge_upload_unusable | 422 | validation_error | The upload reservation is missing, expired or belongs to someone else, so start the upload again. |
| label_limit_reached | 422 | validation_error | The mailbox has as many labels as it may hold, so delete one to create another. |
| label_name_taken | 409 | conflict_error | A label with that name already exists. |
| label_not_directly_settable | 422 | validation_error | That label is set by an action such as trash or snooze, so call the endpoint the message names instead. |
| label_not_found | 422 | validation_error | No label has that id, so list them with GET /labels or create one with POST /labels. |
| last_domain | 409 | conflict_error | This is the last domain in the workspace and removing it deletes the whole mailbox, so remove it in the app, which asks for confirmation first. |
| mailbox_address_invalid | 400 | invalid_request_error | The address given for the mailbox at another provider is not an email address. |
| mailbox_checks_limited | 429 | rate_limit_error | Too many sign-in attempts or lookups for a mailbox at another provider were made in the last hour, so try again later. |
| mailbox_host_not_allowed | 400 | invalid_request_error | The mail server given cannot be used, because it has to be a public host on port 993, or on port 143 with STARTTLS. |
| mailbox_login | 403 | permission_error | A password sign-in for a single address cannot be invited to or join a workspace, so invite the person's own account instead. |
| mailbox_login_failed | 400 | invalid_request_error | The mail server of the mailbox being imported or connected did not accept the address and password, whatever its reason. |
| mailbox_login_forbidden | 403 | permission_error | The token belongs to a password sign-in for a single address, which cannot use apps, so connect the app from a full Nextmail account. |
| mailbox_settings_unknown | 400 | invalid_request_error | No mail server was found for the address of the mailbox being imported or connected, so send its host and port. |
| mailbox_unreachable | 502 | api_error | The mail server of the mailbox being imported or connected did not answer, so check the host and port and try again. |
| mailbox_uses_microsoft | 400 | invalid_request_error | The mailbox signs in with Microsoft and takes no password here, so connect it with the Microsoft sign-in in the app. |
| malformed_json | 400 | invalid_request_error | The request body is not valid JSON. |
| member_is_owner | 422 | validation_error | The workspace owner already holds every permission and cannot be changed or removed this way. |
| member_not_found | 422 | validation_error | That person or domain is not in this workspace. |
| message_too_large | 422 | validation_error | The attachments together are larger than one message may carry. |
| microsoft_not_configured | 503 | api_error | The Microsoft sign-in for imports is not set up on this server. |
| microsoft_sign_in_failed | 400 | invalid_request_error | Microsoft did not approve the sign-in for the import, so start it again. |
| missing_api_key | 401 | authentication_error | No credential was sent, so send an API key or an OAuth access token in the Authorization header as a Bearer token. |
| missing_chunks | 412 | conflict_error | Part of the import upload never arrived, so upload the same files again to finish it. |
| missing_inbox_token | 401 | authentication_error | A disposable inbox call was made without the inbox token the address was created with, so send it as a Bearer token. |
| missing_template_prop | 422 | validation_error | The template requires a prop that was not supplied. |
| no_recipients | 422 | validation_error | Nobody in the chosen audiences can be sent to, because they are empty or every contact has unsubscribed or is suppressed. |
| no_workspace | 422 | validation_error | The workspace does not exist. |
| not_a_calendar_file | 400 | invalid_request_error | The body is not an iCalendar file, so send an .ics file. |
| not_a_contacts_file | 400 | invalid_request_error | The body holds no vCard, so send a .vcf file. |
| not_cancellable | 409 | conflict_error | The import has already finished, so there is nothing to cancel. |
| not_configured | 503 | api_error | Disposable inboxes are unavailable right now: no disposable domain is on offer, or the mailbox behind the pool cannot be read. |
| not_found | 404 | not_found_error | The import does not exist. |
| not_invited | 403 | permission_error | The event did not invite the address named in respondingAs, or none of the addresses the caller may send as, so there is nobody to answer as. |
| not_organizer | 403 | permission_error | Only the organiser can change or cancel this event, so change your own response instead. |
| not_per_address | 422 | validation_error | The scope ?address= names does not keep that setting. An address or a catch-all keeps its own signature, footer, tracking, external images, trusted senders and blocklist, and @domain only the last three, so send the setting to a scope that keeps it, or without ?address=. |
| not_resumable | 409 | conflict_error | The import is not waiting, or it came from a Microsoft sign-in, so there is nothing this call can resume. |
| not_revoked | 409 | conflict_error | The API key has to be revoked before it can be deleted. |
| not_uploading | 409 | conflict_error | The import is no longer taking uploads. |
| nothing_selected | 400 | invalid_request_error | The import from another provider chose nothing it can bring across, so choose at least one kind of thing the provider holds and the key can read. |
| operator_domain | 409 | conflict_error | The domain belongs to Nextmail itself, so it cannot be added as a workspace domain. |
| owner_only | 403 | permission_error | Only the workspace owner can do this, or for webhooks a member whose role reaches every address. The token acts for a member who may not, so ask the owner, or use a key the owner created. |
| password_required | 400 | invalid_request_error | The import stopped because the old mailbox refused its password, so send the new password to resume it. |
| pay_as_you_go_not_enrolled | 409 | conflict_error | Pay as you go has never been set up on this workspace, so it has no limit to change yet. Turn it on with PATCH /billing/pay-as-you-go first. |
| plan_conflict | 409 | conflict_error | The workspace already pays for a plan, or a checkout for it could not be started, as the message says. Change the plan on the billing portal, POST /billing/portal, rather than buying a second one. |
| plan_not_cancelled | 502 | api_error | The plan on the workspace could not be cancelled, so the workspace was kept. Try again in a moment. |
| plan_required | 403 | permission_error | The plan of the workspace does not include team members, a web app address on its own domain or labels that AI applies, as the message says, so upgrade it first. |
| plan_unavailable | 422 | validation_error | That plan is not on sale, or not at that interval, so choose one GET /billing/plans lists, with an interval it offers. |
| proposal_not_found | 404 | not_found_error | That guest has not proposed a new time for the event. |
| rate_limited | 429 | rate_limit_error | The key or app sent more requests in a minute than it may. Retry-After says how many seconds to wait, and the RateLimit-Remaining and RateLimit-Reset headers on every answer show how close a key is to the limit. |
| recipient_not_found | 404 | not_found_error | The broadcast has no copy with that emailId. |
| reject_needs_envelope | 422 | validation_error | A rule that refuses mail at SMTP has to test envelope_from, because a From header match bounces the mailing list rather than the author. |
| related_domain_owned | 409 | conflict_error | A related domain belongs to someone else, so this domain cannot be added here. |
| replay_in_progress | 409 | conflict_error | A replay of this event is being sent right now, so read its result before replaying again. |
| reply_not_sent | 409 | conflict_error | Your response could not be sent to the organiser, and the message says why. |
| rerun_not_available | 409 | conflict_error | The import named in rerunOf is not a finished import of the same mailbox into the same address from the last 30 days. |
| research_limit_reached | 429 | rate_limit_error | The workspace has looked up as many senders as it may today, so try again tomorrow. |
| resend_unavailable | 409 | conflict_error | The broadcast cannot go again to the people who did not open it: it has not finished going out, or it did not track opens. |
| reserved_address | 422 | validation_error | That name has to stay reachable on the domain, so it cannot be issued as a disposable address. |
| reserved_header | 422 | validation_error | The header is set by Nextmail or is not one a message may set, so leave it out. |
| resource_not_found | 404 | not_found_error | Nothing with that id exists in this workspace, and a 404 never says whether it belongs to another one. |
| restricted_key | 400 | invalid_request_error | The provider key can only send, so create a key with full access there, import, then revoke it. |
| retry_in_progress | 409 | conflict_error | An automatic retry of this event is being sent right now, so wait a few seconds and replay only if it fails. |
| revoked | 409 | conflict_error | The API key was revoked, so it cannot be rotated, switched back on or changed, and a new one has to be created. |
| revoked_api_key | 401 | authentication_error | The API key was revoked, or the workspace it belongs to no longer exists, so create a new key. |
| role_immutable | 409 | conflict_error | The owner role cannot be changed or handed out, so transfer the workspace instead. |
| role_in_use | 409 | conflict_error | Members, keys or invitations still hold the role, so name a role to move them to. |
| role_name_taken | 409 | conflict_error | A role with that name already exists in the workspace. |
| role_not_found | 404, 422 | not_found_error, validation_error | No role in this workspace has that id. |
| role_undeletable | 409 | conflict_error | The owner role cannot be deleted. |
| rule_name_taken | 409 | conflict_error | A rule with that name already exists on the mailbox. |
| segment_limit_reached | 409 | conflict_error | The workspace already has 100 segments, the most it may keep. Delete one before you save another. |
| segment_not_found | 404 | not_found_error | No segment with that id in this workspace, or it was deleted. |
| segment_too_large | 422 | validation_error | A segment with a custom field condition reads each contact it checks, and stops at 20000 of them. Narrow the audiences it applies to, or match on another field. |
| send_quota_exceeded | 429 | rate_limit_error | The workspace has used the monthly send allowance its plan includes, which resets on the first of the month, so show it to a person rather than retrying. |
| sender_category_limit_reached | 422 | validation_error | The workspace holds as many sender choices as it may, so remove one to add another. |
| setup_email_not_sent | 503 | api_error | The setup email could not be sent. Nothing was created, so try again. |
| setup_email_rate_limited | 429 | rate_limit_error | A workspace can email 50 mail app setup links a day, and it has. Wait until tomorrow, or create an app password and hand it over yourself. |
| signature_limit_reached | 422 | validation_error | The address holds 50 signatures, as many as one may, so delete one to add another. |
| signature_name_taken | 409 | conflict_error | The address already has a signature with that name, compared without case. |
| signature_too_long | 422 | validation_error | The signature is longer than a signature may be, before or after its HTML is cleaned up. |
| smtp_rate_limited | 429 | rate_limit_error | A key can hand over 300 messages a minute over SMTP, and it has. The message was refused with a temporary 451, so mail software tries again by itself. It shows in the request log and is never an HTTP response. |
| step_up_code_expired | 400 | invalid_request_error | The verification code ran out after 10 minutes, or none was waiting, so ask for a new one with POST /security/step-up. |
| step_up_code_invalid | 400 | invalid_request_error | The verification code did not match, and the message says how many tries are left. |
| step_up_locked | 429 | rate_limit_error | Too many wrong verification codes, so start a fresh code, or wait until the time the message gives if verification is paused. |
| step_up_not_applicable | 400 | invalid_request_error | An API key called a verification endpoint, and keys are never asked for a code. |
| step_up_required | 403 | permission_error | An OAuth app has to verify a code from the person it acts for before this change, then repeat the call once. The Verification codes section of this page shows how. |
| step_up_throttled | 429 | rate_limit_error | The app has asked for too many verification codes lately, and the message says when to try again. |
| step_up_undeliverable | 503 | api_error | The email carrying the verification code could not be sent and no code is waiting, so try again in a moment. |
| still_running | 409 | conflict_error | The import is still running, so cancel it before deleting its upload. |
| storage_host_in_use | 409 | conflict_error | The files host is already used by another domain or server, and the message says where. |
| stream_locked | 409 | conflict_error | The lane paused itself 3 times in 30 days, so only Nextmail support can resume it. Write to [email protected] once the cause is fixed. |
| stream_not_paused | 409 | conflict_error | The lane is not paused, so there is nothing to resume. |
| stream_paused | 409 | conflict_error | The lane the message travels on is paused for this workspace because too much of its mail bounced or was reported as spam over the last 7 days. Each lane pauses on its own, so the other one still goes out. GET /sending/streams shows the numbers, and POST /sending/streams/{stream}/resume resumes the lane once the list is clean. |
| support_rate_limited | 429 | rate_limit_error | One address can send 3 messages to support in 10 minutes, and they all arrived, so wait a few minutes before sending another. |
| suppression_not_removable | 409 | conflict_error | Mail to the address could not be delivered, so it cannot be allowed again from here, and the corrected address should be used instead. |
| task_active_limit_reached | 403 | permission_error | The plan of the workspace covers no more active tasks, so pause another task or upgrade before turning this one on. |
| task_approval_decided | 409 | conflict_error | The approval request was already answered, has expired, or belongs to a run that has ended. |
| task_approval_not_found | 404 | not_found_error | No approval request in this workspace has that id. |
| task_limit_reached | 403 | permission_error | The workspace holds as many tasks as it may, so delete one before adding another. |
| task_not_found | 404 | not_found_error | No task in this workspace has that id. |
| task_run_finished | 409 | conflict_error | The run has already ended, so there is nothing left to cancel. |
| task_run_limit_reached | 409 | conflict_error | The task has run as many times as it may in a day, or the workspace has started as many one-off runs as it may in a day. |
| task_run_not_found | 404 | not_found_error | No task run in this workspace has that id. |
| temp_inbox_rate_limited | 429 | rate_limit_error | One network created more disposable inboxes than it may in a few minutes. Wait for the number of seconds in Retry-After, or reuse an inbox you already hold. |
| template_archived | 422 | validation_error | The template is archived, so restore it before sending with it. |
| template_in_use | 409 | conflict_error | A broadcast that has not started yet uses the template, so cancel the broadcast or wait until it starts sending. |
| template_name_taken | 409 | conflict_error | A template with that name already exists. |
| template_not_found | 422 | validation_error | No template has that id or slug. |
| template_not_published | 422 | validation_error | The template or the chosen version is a draft, so publish it before sending with it. |
| template_slug_taken | 409 | conflict_error | A template with that slug already exists. |
| template_version_not_deletable | 422 | validation_error | The version is the only one, the live one or the one being edited, so it cannot be deleted. |
| template_version_not_found | 422 | validation_error | The template has no such version. |
| thread_delete_failed | 500 | api_error | The thread could not be deleted and nothing was removed, so try again. |
| thread_update_failed | 500 | api_error | The label changes could not be written and nothing on the thread changed, so try again. |
| too_many_events | 400 | invalid_request_error | The calendar file holds more than 25,000 events, so split it and import each part. |
| too_many_files | 400 | invalid_request_error | An import takes at most 50 files. |
| too_many_items | 422 | validation_error | A batch holds at most 100 messages, and fewer when they use translate. |
| too_many_recipients | 422 | validation_error | The message has more recipients than one send allows. |
| tracking_host_in_use | 409 | conflict_error | The tracking host is already used by another domain or server, and the message says where. |
| translation_failed | 503 | api_error | The message could not be translated and nothing was sent, so try again or send without translate. |
| translation_locked | 409 | conflict_error | The message was translated when it was accepted, so its wording can no longer be edited, and it has to be cancelled and sent again. |
| translation_not_configured | 409 | conflict_error | Translation is not available here, so nothing was sent, and it can go out as written without translate. |
| translation_too_long | 422 | validation_error | The message is too long to translate. |
| two_factor_required | 403 | permission_error | The workspace asks everyone in it to sign in with two-factor authentication, so turn it on for the account in the app before joining. |
| unknown_domain | 422 | validation_error | The domain is not offered for disposable inboxes. |
| unknown_language | 422 | validation_error | translate.to is not a language messages can be translated into. |
| unknown_parameter | 422 | validation_error | The request names a field this endpoint does not accept. |
| unknown_template_prop | 422 | validation_error | The template declares no prop with that name. |
| unknown_template_slot | 422 | validation_error | The template declares no slot with that name. |
| unreachable | 503 | api_error | Something the call had to reach did not answer, such as the provider during an import or public DNS during a domain check, so try again in a moment. |
| unsubscribe_not_removable | 409 | conflict_error | The person unsubscribed themselves, or complained, or left at another provider, so only they can subscribe again, through a sign-up form they confirm from their inbox or the Subscribe again button on the unsubscribe page. |
| unsubscribe_refused | 502 | api_error | The sender did not accept the one-click unsubscribe and gives no address to write to, so nothing changed. Try again later, or open its unsubscribe link. |
| unsubscribe_unsupported | 422 | validation_error | The sender gives no way to unsubscribe, so nothing was sent and the subscription is unchanged. |
| unsupported_file | 400 | invalid_request_error | One of the import files is not a .zip, .tgz, .mbox or .eml export, so there is nothing in it to import. |
| upload_dangerous | 400 | invalid_request_error | That type of file cannot be stored here. |
| upload_empty | 400 | invalid_request_error | The uploaded file is empty. |
| upload_failed | 502 | api_error | The file could not be saved, or its upload reservation is missing or has expired, so start the upload again. |
| upload_forbidden | 403 | permission_error | The upload was reserved by someone else or in another workspace. |
| upload_no_address | 403 | permission_error | Uploading files needs access to at least one address in the workspace. |
| upload_no_name | 400 | invalid_request_error | The upload gave no file name, so send it in the X-Filename header or the filename query parameter. |
| upload_rate_limited | 429 | rate_limit_error | Too many files were uploaded in the last hour, so try again later. |
| upload_storage_full | 507 | api_error | Uploaded files in the workspace have reached their storage limit, so delete some before uploading more. |
| upload_too_large | 413 | invalid_request_error | The file is larger than the upload limit the message gives. |
| user_not_found | 404 | not_found_error | No account on Nextmail has that userId. |
| username_locked | 409 | conflict_error | A username is permanent once it is chosen, so it cannot be changed. Contact support if you need a different one. |
| username_taken | 409 | conflict_error | Another account already has that username, so choose a different one. |
| version_conflict | 409 | conflict_error | Someone saved the template, form or automation after it was read, so reload it and make the change again. |
| webhook_disabled | 409 | conflict_error | The webhook is switched off, so switch it back on to replay its deliveries. |
| workspace_allowance_reached | 403 | permission_error | The account already owns a workspace on the Free plan, and an account can own one, so upgrade that workspace before creating another. |
| workspace_limit_reached | 422 | validation_error | The workspace has reached a soft limit on that kind of resource, as Workspace limits lists, and the message names it. |
| workspace_suspended | 403 | permission_error | The workspace is suspended, or its owner is, so it cannot send mail or use the API until the suspension is lifted. Write to [email protected] to ask why or to appeal. |
Rate limits
Each API key, and each access token an app holds, may make 600 requests a minute, counted across every endpoint. The minute is a fixed window that starts on the minute of the clock, and every answer to a key or an app carries the count, so a client can slow down before it is refused.
| Header | What it says |
|---|---|
| RateLimit-Policy | The limit as requests and a window in seconds: 600;w=60. |
| RateLimit-Limit | How many requests the key or app may make in one window. |
| RateLimit-Remaining | How many are left in the current window, this request counted. |
| RateLimit-Reset | Seconds until the window ends and the count starts again. |
| Retry-After | Only on a 429: the seconds to wait before the same request can succeed. |
The request after the last one a window allows is a 429 rate_limited, and its Retry-After is the rest of the window. Nothing it asked for was done, so send it again once the wait is over. Every other 429 carries Retry-After as well: the length of its own window for a throttle such as upload_rate_limited, the seconds to midnight UTC for a daily allowance such as ai_quota_exceeded, and the seconds to the first of the month for send_quota_exceeded.
Some endpoints keep a tighter limit of their own on top. POST /events takes 600 events a minute from a key or an app, and POST /temp-mail/inboxes, which needs no key, lets one network create 10 inboxes every 10 minutes. Their rows in Every error code say what each one answers.