This is the shortest path from an empty account to a message that actually lands in an inbox. Follow it in order — each step unlocks the next, and skipping domain authentication is the single most common reason a first send never arrives.
Before you start
You need three things: an account, a sending identity you control, and a token. Everything else in this guide is a consequence of those three.
- An email domain you can edit DNS for — a subdomain such as mail.yourcompany.com is the recommended setup.
- A terminal with curl, or any HTTP client. There is no SDK requirement: the API is plain REST over JSON.
- Roughly ten minutes, plus DNS propagation time, which is out of anyone’s control.
1. Create your account
Sign up and you get a tenant with an isolated project, a wallet and a test environment. Nothing is billed until you send: pricing is per message, and the panel shows the estimated cost of every request before it leaves the queue.
A new account starts in the test environment. Test tokens are prefixed qsy_test_ and never reach a real inbox — they exercise the full pipeline, including webhooks, without spending balance.
2. Authenticate your sending domain
Mailbox providers do not care who you are; they care whether the domain in the From address vouches for the server that sent the message. That proof is DNS, and it is not optional.
curl -X POST https://api.qsendyx.com/api/v1/email/domains \
-H "Authorization: Bearer $QSENDYX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "domain": "mail.yourcompany.com" }' - 1 SPF Authorises our infrastructure to send on behalf of the domain. One SPF record per domain — a second one invalidates both.
- 2 DKIM Signs each message with a private key so the recipient can verify it was not altered in transit. We generate the key pair; you publish the public half.
- 3 DMARC Tells providers what to do when SPF and DKIM fail, and where to send the reports. Start at p=none, read the reports for a couple of weeks, then tighten.
Publish the records, then trigger verification. Propagation usually takes minutes, sometimes hours. Verification is idempotent: run it as often as you like.
3. Create an API token
Tokens carry scopes, and scopes are the blast radius of a leak. A token that only needs to send email should never be able to read your billing or manage webhooks.
| Scope | Grants |
|---|---|
| email:send | Send email, including template-based sends |
| email:read | Read message status and events |
| email:domains | Register and verify sending domains |
| sms:send | Send SMS |
| sms:read | Read SMS status and events |
| webhooks:manage | Create, rotate and inspect webhook endpoints |
| analytics:read | Read aggregate delivery metrics |
Authorization: Bearer qsy_live_91fb5d2dc31fd065... 4. Send your first message
One POST. The from address must belong to a verified domain or a confirmed sender, otherwise the request is rejected before anything is charged.
curl -X POST https://api.qsendyx.com/api/v1/email/messages \
-H "Authorization: Bearer $QSENDYX_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"from": { "email": "you@yourcompany.com", "name": "Your Company" },
"to": [{ "email": "user@example.com" }],
"subject": "Welcome aboard",
"html": "<h1>You are in.</h1>"
}'{
"id": "019f7141-46ac-76c3-8276-042f38c68cd8",
"status": "queued",
"channel": "email",
"to": "user@example.com",
"from": "you@yourcompany.com",
"type": "transactional",
"created_at": "2026-07-17T18:05:31Z",
"estimated_cost_micros": 0,
"currency": "USD"
} A 202 means we took responsibility for the message, reserved the balance and put it on a queue. Delivery is asynchronous by design: an SMTP conversation with a remote provider can take seconds, and no API should hold your request open for that.
5. Track what happened
Two ways to find out how a message ended, and you should use both. Polling answers "what about this one?"; webhooks answer "tell me when anything changes".
curl https://api.qsendyx.com/api/v1/email/messages/019f7141-46ac-76c3-8276-042f38c68cd8 \
-H "Authorization: Bearer $QSENDYX_TOKEN" The lifecycle is queued → sent → delivered, with bounced, failed, rejected and suppressed as terminal alternatives. Engagement events — opened, clicked, unsubscribed — arrive after delivery and do not change the delivery status.
6. Receive events on your endpoint
Register an HTTPS endpoint and we POST every event you subscribed to, signed with a per-endpoint secret. Respond 2xx quickly and do the real work asynchronously — a slow endpoint gets retried, and retries look like duplicates.
{
"id": "evt_3f9a1c72b0d84e15a6c27f31",
"type": "message.delivered",
"version": "1",
"occurred_at": "2026-07-17T18:05:36.918Z",
"tenant_id": "019f70a4-3c8e-7d21-b0f4-51ac9e77b201",
"project_id": "019f70a4-3c8e-7d21-b0f4-51ac9e77b3aa",
"resource_id": "019f7141-46ac-76c3-8276-042f38c68cd8",
"data": {
"message_id": "019f7141-46ac-76c3-8276-042f38c68cd8",
"status": "delivered",
"channel": "email",
"message_type": "transactional",
"recipient": "user@example.com"
}
} Verify the signature before you trust a single field. The HMAC covers the timestamp and the raw body, so a captured request cannot be replayed against you forever.
import crypto from "node:crypto";
// O corpo BRUTO, antes de qualquer JSON.parse — reserializar muda os bytes e quebra o HMAC.
export function verify(rawBody, headers, secret) {
const timestamp = headers["x-webhook-timestamp"];
const signature = headers["x-webhook-signature"];
// Sem a janela de tolerância, um webhook capturado é replicável para sempre.
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!timestamp || age > 300) return false;
const expected =
"v1=" +
crypto
.createHmac("sha256", secret)
.update(`${timestamp}.` + rawBody)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}7. Go live
Swap the test token for a live one and keep an eye on the first few thousand messages. Volume earns reputation; volume that arrives all at once earns suspicion.
- Ramp gradually. A domain that sent nothing yesterday and fifty thousand today looks exactly like a compromised account.
- Watch bounces and complaints, not just deliveries. A hard bounce rate above a couple of percent is a list-quality problem, and it is being counted against you.
- Honour unsubscribes immediately — we suppress them automatically, and a suppressed recipient is never sent to again.
- Keep transactional and marketing on separate identities. One bad campaign should not take your password resets down with it.