SMTP relay
Point an existing application's SMTP settings at zSender instead of calling the HTTP API. This page covers the connection settings, what the relay does to your raw message on the way through, and the codes it answers with when it rejects one.
Connect
zSender runs its own SMTP submission server. This is not the mail server's own SMTP port, and it is the only supported way in: a credential for that port would authenticate to the shared server behind every tenant and bypass zSender's quota, suppression, and domain checks entirely.
| Setting | Value |
|---|---|
| Host | smtp.zsender.io |
| Port | 587 or 2525: identical, pick 2525 if your network blocks 587. |
| Security | STARTTLS. AUTH is offered only after the STARTTLS upgrade: there is no unencrypted submission and no implicit-TLS (465) port. |
Credentials
The username is the literal string apikey: it isn't checked, only the password is. The password is your zSender API key, the same one you use as a bearer token over HTTP; it needs the send permission (or admin, which implies it). A zst_ test-mode key authenticates and works here identically to a live key. See Test mode for what "identically" leaves out (it still routes to the sandbox, not a real recipient). Full key shape and permission model: Authentication.
Host: smtp.zsender.io
Port: 587 (or 2525)
Security: STARTTLS
Username: apikey
Password: <your zSender API key>What the relay does to your message
The relay is a thin protocol adapter, not a second copy of the HTTP send pipeline. It drains the raw RFC 822 bytes your client sends over DATA and hands them to the mail server unchanged: the body is never parsed or rebuilt. That's deliberate: rebuilding a MIME message breaks DKIM alignment and forces inline cid: images through a lossy rewrite to data: URIs. Passthrough means your DKIM signature (if you sign client-side) and every inline image survive the hop intact.
Everything zSender enforces (sender-domain verification, quota, suppression, unsubscribe handling, tagging) runs at the envelope and single-header level: it reads or injects individual headers on the raw buffer without ever building a MIME tree. Two things it changes before relaying:
- A
Message-IDis injected, but only when your message doesn't already have one. This isn't cosmetic: the mail server derives themessage_idit hands back from the message's ownMessage-IDheader. Send without one and that response field comes backnull, which breaks the delivery record zSender keeps for the send. The injected value is set before signing, so it's still covered by DKIM. X-Zsender-Transactional: true: the SMTP form of the HTTP API'stransactionalflag, with the same exact two effects (see Transactional mail). It's a control header addressed to zSender, not to your recipient, so it is always stripped before relaying: set it and it will never reach the recipient's mailbox, whether or not it changed anything.
A List-Unsubscribe header is added the same way as over HTTP, only when your message doesn't already carry one, and never when X-Zsender-Transactional is set. An X-Tag header, if present, is read for the message's tag (the same value tag plays over HTTP. See Tags) and relayed onward as X-Postal-Tag so the mail server's own record agrees with zSender's.
Reserved headers, over SMTP
The HTTP API's reserved-header list exists to police a structured headers field: there is no equivalent field here, because you write the entire raw message yourself, so nothing on this path rejects a header name the way POST /v1/emails/send does. Sender-domain verification and account-email verification apply exactly as they do over HTTP (see Domains), and a domain that isn't verified, or an unverified account email, is rejected before the message ever reaches the mail server.
Recipients: envelope-authoritative, and a different cap
Delivery goes to exactly the SMTP envelope's RCPT TO set: zSender never inspects the To/Cc/Bcc headers inside your message to decide who receives it, so a Bcc-only send works exactly like any other.
The cap is different from the HTTP API's, and the two don't match. Sending email caps to, cc, and bcc at 50 each: up to 150 recipients in one call. Here the cap is on the envelope: a combined 50 RCPT TO recipients total, across whatever mix of To/Cc/Bcc your message declares. Go over it and the relay answers 550. If you're porting a send from one transport to the other, don't assume the limit carries over unchanged.
Errors
A rejection is an SMTP reply code plus a short message. The gateway only registers AUTH and DATA handlers. There is no per-command MAIL FROM/RCPT TO handler, so envelope checks, including an empty MAIL FROM and the 50-recipient cap above, are both evaluated at the DATA stage, after the whole message has been read: expect 250 at every MAIL FROM/RCPT TO and any rejection only after DATA. The internal error is never echoed back, only these fixed reasons:
| Code | Cause |
|---|---|
| 550 | Sender domain is not verified for this tenant. |
| 550 | Account email is not verified. |
| 550 | More than 50 RCPT TO recipients on the envelope. |
| 550 | Every recipient on the envelope is on your suppression list. |
| 553 | MAIL FROM is empty. |
| 452 | Monthly quota exceeded, or the per-minute rate limit was hit. |
| 451 | The upstream mail server timed out or errored (temporary). |
| 552 | Message exceeds the size cap (25 MB by default). |
550 and 553 are permanent. Fix the request and send again, don't retry as-is. 452 and 451 are temporary: your MTA should retry later rather than drop the message. A test-mode send draws from its own fixed rate bucket rather than your plan's per-minute limit, so testing heavily doesn't eat into the allowance your live traffic depends on.
A fully-suppressed send is rejected, not silently accepted. If every recipient on the envelope is on your suppression list, the message is deliverable to nobody and the relay answers 550 All recipients are suppressed rather than taking responsibility for a delivery that will not happen. Partial suppression is different: if at least one recipient is deliverable the message is relayed to those recipients and you get a normal 250. SMTP has no way to say "I dropped two of these," and refusing the whole message to convey it would lose mail that was fine, so check the message afterwards, or use the HTTP API, whose response carries a per-recipient suppressed array. See What a send does when it hits a suppressed address for why, and check GET /v1/suppressions/:email beforehand, or send through the HTTP API instead, when you need to know.
Next
For the fields the HTTP send API accepts (threading, tags, and exactly what transactional does) see Sending email. To get a domain's sending capability to VERIFIED before you point an app at this relay, see Domains. To try the relay without sending real mail, see Test mode.