Enable email routing
Auth: Authorization: Bearer nsin_… or X-Api-Key: nsin_… — see API keys.
Publishes the MX, SPF and DKIM records and turns routing on. Any MX
records the domain already has must be removed (they would route mail
elsewhere) — send remove_existing_mx: true to agree, otherwise the
call answers 409 with conflict: mx and the rows. An existing SPF
record is merged rather than duplicated (merge_spf: true); two SPF
records answer 409 with conflict: spf_multiple. The removed MX rows
are kept so a later disable can put them back.
Refused while no mail host is ready (503, code: hosts_not_ready),
on external DNS (409, conflict: external_dns), on a domain with no
active plan (402) and on a plan without the feature (403).
Idempotent: an enabled domain answers 200 unchanged. Requires
domain.settings.
Path parameters
- domainstringrequiredpath
The domain name (for example
example.com) — not a numeric id.
Request body
Content type: application/json
- remove_existing_mxboolean
Agree to remove the domain's existing MX records (they are snapshotted for disable).
- merge_spfboolean
Agree to merge our include term into the domain's existing SPF record.
Responses
200Routing enabled; the status.
Content type: application/json
- enabledboolean
Whether Email Routing is switched on for the domain.
- statusstring
unconfigured— not enabled.ready— enabled, and the zone carries the MX, SPF and DKIM records mail needs.misconfigured— enabled, but the last DNS check found a record missing or a foreign one in the way (seedns).unlocked— the managed records were unlocked for editing (POST .../email-routing/unlock).suspended— forwarding is paused by NSIN (suspended_reasonsays why).One of:"unconfigured""ready""misconfigured""unlocked""suspended" - hosts_readyboolean
At least one mail host has checked in recently. Enable is refused while false.
- lockedboolean
The managed records are not editable.
- subaddressingboolean
user+tag@addresses are delivered to theuser@rule. Set withPUT .../email-routing/settings. - suspended_reasonstring
Why forwarding is suspended —
over_capwhen the plan's daily forward allowance is used up, otherwise NSIN's note. Empty when not suspended. - catch_allEmailCatchAll
Fields
- enabledboolean
Whether the catch-all handles mail for addresses you have not created. Off, such mail is refused as an unknown user.
- actionstring
forwardto the destination, ordrop(accept and discard).One of:"forward""drop" - destination_idinteger | null
The destination for
forward. - destination_emailstring | null
That destination's address.
- destination_verifiedboolean
Whether the destination has confirmed its address.
- dnsobject
The DNS records mail needs, and what the last check found.
Fields
- checked_atstring | null (date-time)
When DNS was last checked;
nullbefore the first check. - okboolean
Every required record was found and nothing conflicting was.
- recordsarray of EmailDNSRecord
Item fields
- typestring
Record type.
One of:"MX""TXT" - namestring
Relative to the domain;
@is the apex. - contentstring
The value the record must have.
- priorityinteger
MX priority.
- ttlinteger
TTL in seconds.
- statestring
extrais a foreign MX or a second SPF seen publicly.One of:"ok""missing""extra""unchecked" - record_idinteger
The managed record's id, when the row is ours.
- problemsarray of string
Human-readable findings of the last check.
- conflictsobject | null
Only while unconfigured — what enable would have to remove or merge.
Fields
- mxarray of EmailConflictRecord
Item fields
- idinteger
- namestring
- contentstring
- priorityinteger
- spfobject | null
Fields
- record_idinteger
- contentstring
- mergedstring
- spf_multipleboolean
- previous_mx_availableboolean
Enable removed MX records that disable can restore.
- counts_24hEmailCounts
Fields
- receivedinteger
Every message except discarded bounces.
- forwardedinteger
Messages delivered to a destination.
- droppedinteger
Messages accepted and discarded by a
droprule or catch-all. - rejectedinteger
Messages refused.
- deferredinteger
Messages temporarily refused (the sender retries), for example over the daily forward cap.
- sentinteger
Recipients that mail sent from this domain (the send endpoint) was delivered to. Present on the status counters only.
- limitsobject
The plan's Email Routing allowances and today's use.
Fields
- max_rulesinteger | null
Custom addresses the plan allows on this domain.
nullmeans unlimited. - rules_usedinteger
Custom addresses the domain holds now.
- max_forwards_per_dayinteger | null
Messages the plan lets this domain forward per UTC day.
nullmeans unlimited. - forwards_todayinteger
Messages forwarded so far today (UTC).
- sendingEmailSendingStatus
Whether this domain may send mail right now, and how much of today's allowance is used.
Fields
- plan_enabledboolean
The plan includes sending (
email_sending_enabled). - allowedboolean
A send would be accepted now (limits aside).
- reasonstring
Why not, when
allowedis false:platform_off,plan,not_enabledorsuspended. Empty otherwise. - max_per_dayinteger | null
Recipients the plan lets this domain send to per UTC day.
nullmeans unlimited. - sent_todayinteger
Recipients queued so far today (UTC)
- max_recipientsinteger
Recipients allowed in one message.
- max_text_kbinteger
Largest message text
- max_subjectinteger
Longest subject
- dkimobject
Always present. Before Email Routing is enabled the selector and name are already known (they come from platform settings) and
txtis empty, because the key is generated at enable time.Fields
- selectorstring
The DKIM selector.
- namestring
The TXT name relative to the domain.
- txtstring
The DKIM TXT value (
v=DKIM1; …). Empty until enabled.
- platformEmailPlatform
NSIN's mail platform — what the domain's records point at.
Fields
- mx_hostsarray of object
The MX targets published for the domain.
Item fields
- hoststring
- priorityinteger
- spf_includestring
The name the domain's SPF record must
include:. - forward_domainstring
Domain the forwarded mail's envelope sender is rewritten to (SRS).
{
"enabled": true,
"status": "unconfigured",
"hosts_ready": true,
"locked": true,
"subaddressing": true,
"suspended_reason": "string",
"catch_all": {
"enabled": true,
"action": "forward",
"destination_id": 0,
"destination_email": "string",
"destination_verified": true
},
"dns": {
"checked_at": "2026-01-01T00:00:00Z",
"ok": true,
"records": [
{
"type": "MX",
"name": "string",
"content": "string",
"priority": 0,
"ttl": 0,
"state": "ok",
"record_id": 0
}
],
"problems": [
"string"
]
},
"conflicts": {
"mx": [
{
"id": 0,
"name": "string",
"content": "string",
"priority": 0
}
],
"spf": {
"record_id": 0,
"content": "string",
"merged": "string"
},
"spf_multiple": true
},
"previous_mx_available": true,
"counts_24h": {
"received": 0,
"forwarded": 0,
"dropped": 0,
"rejected": 0,
"deferred": 0,
"sent": 0
},
"limits": {
"max_rules": 0,
"rules_used": 0,
"max_forwards_per_day": 0,
"forwards_today": 0
},
"sending": {
"plan_enabled": true,
"allowed": true,
"reason": "string",
"max_per_day": 0,
"sent_today": 0,
"max_recipients": 0,
"max_text_kb": 0,
"max_subject": 0
},
"dkim": {
"selector": "string",
"name": "string",
"txt": "string"
},
"platform": {
"mx_hosts": [
{
"host": "string",
"priority": 0
}
],
"spf_include": "string",
"forward_domain": "string"
}
}401Missing, malformed, revoked or expired API key. (A valid key whose
account has been deactivated gets 403 account is inactive instead.)
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
{
"error": "invalid API key"
}402The domain has no active plan. The body carries upgrade_url.
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
- max_rulesinteger | null
- upgrade_urlstring
{
"error": "This domain does not have an active plan. Purchase a plan for this domain before adding email addresses.",
"upgrade_url": "/plans"
}403The key is read-only, your role lacks the permission, the plan does not
include Email Routing (upgrade_url), or the plan's max_email_rules
is reached — the body then carries max_rules and upgrade_url.
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
- max_rulesinteger | null
- upgrade_urlstring
{
"error": "You have reached the maximum number of email addresses for this domain's plan. Upgrade the plan for this domain to add more.",
"max_rules": 50,
"upgrade_url": "/plans"
}404No such domain, or it is not visible to this account. Domains you cannot access are reported as not found rather than forbidden.
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
{
"error": "read-only API key"
}409Something in the zone has to change first. conflict says what:
mx (existing MX records, listed under records — re-send with
remove_existing_mx: true), spf (an existing SPF record, with the
merged preview — re-send with merge_spf: true), spf_multiple
(two SPF records; keep one), external_dns (the domain does not use
NSIN DNS) or domain_status (the domain is disabled).
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
- conflictstring
What conflicts (
mx,spf,spf_multiple,external_dns,domain_status,not_enabled,local_part,destination). - recordsarray of EmailConflictRecord
For
mx/spf_multiple— the rows in the way.Item fields
- idinteger
- namestring
- contentstring
- priorityinteger
- record_idinteger
For
spf— the existing SPF row. - contentstring
For
spf— its current value. - mergedstring
For
spf— what it becomes with our include merged in. - destinationEmailDestination
Fields
- idinteger
- emailstring
- verifiedboolean
Whether the address was confirmed through the emailed link.
- verified_atstring | null (date-time)
When it was confirmed.
- created_atstring (date-time)
- last_sent_atstring | null (date-time)
When the last verification mail went out.
- resend_cooldown_secondsinteger
Seconds until another verification mail may be sent; 0 when allowed now.
- in_useinteger
Rules and catch-alls pointing at it.
- last_failurestring
The receiving server's last refusal, when forwards to it are bouncing.
- last_failure_atstring | null (date-time)
When
last_failurewas recorded.
{
"error": "this domain already has MX records; enabling email routing replaces them",
"conflict": "mx",
"records": [
{
"id": 12,
"name": "@",
"content": "mail.example.ir.",
"priority": 10
}
]
}429The key exceeded its request budget (300 requests per minute by default).
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
{
"error": "rate limit exceeded"
}502The nameservers could not be updated; nothing was changed. Retry in a moment.
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
{
"error": "read-only API key"
}503No mail host has checked in recently (code: hosts_not_ready), or
the platform is missing its key-encryption key (code: kek_unset).
Content type: application/json
- errorstringrequired
Human-readable description of what went wrong.
- codestring
Machine-readable reason (
hosts_not_ready,kek_unset,loop,no_mx,unverified). - fieldstring
The offending body field, when one applies.
{
"error": "string",
"code": "string",
"field": "string"
}Example request
curl -X POST "https://api.nsin.ir/domains/example.com/email-routing/enable" \
-H "Authorization: Bearer $NSIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"remove_existing_mx": true,
"merge_spf": true
}'