SendPost App
Introduction
The SendPost App for Flows enables you to connect your workflows to SendPost, a transactional email / email-infrastructure platform. SendPost is the developer-facing sending API from the SendX family: you send transactional mail over a REST API (or SMTP), and it gives you deliverability tooling on top — sending-domain authentication (DKIM / Return-Path / DMARC / tracking CNAME), suppression lists, dedicated IPs and IP pools with auto-warmup, per-sub-account statistics, message inspection and event webhooks.
What is SendPost?
SendPost is an email infrastructure platform that lets you send transactional email and manage sending domains, suppressions, sub-accounts, stats and webhooks through its API. Key features include:
- Transactional email sending (direct HTML or template-based)
- Sending-domain authentication (DKIM / Return-Path / DMARC / tracking CNAME)
- Suppression list management
- Multi-tenant sub-accounts, each with its own API key
- Per-sub-account statistics (aggregate and daily)
- Message inspection and event webhooks
- Dedicated IPs and IP pools with auto-warmup
Getting Started
Prerequisites
Before using the SendPost App, you will need:
- A SendPost account
- Your SendPost Sub-Account API Key (required for sending email and managing that sub-account's domains/suppressions)
- Your SendPost Account API Key (optional — only required for account-level endpoints: sub-accounts, statistics, message lookup, webhooks, IPs)
Available Stages
1. Send Email — POST /api/v1/subaccount/email/ Sub-Account key;
2. Send Email With Template — POST /api/v1/subaccount/email/template Sub-Account key;
3. List Domains — GET /api/v1/subaccount/domain Sub-Account key;
4. Create Domain — POST /api/v1/subaccount/domain Sub-Account key;
5. Get Domain — GET /api/v1/subaccount/domain/{domain_id} Sub-Account key;
6. Delete Domain — DELETE /api/v1/subaccount/domain/{domain_id} Sub-Account key;
7. List Suppressions — GET /api/v1/subaccount/suppression Sub-Account key;
8. Add Suppression — POST /api/v1/subaccount/suppression Sub-Account key;
9. Remove Suppression — DELETE /api/v1/subaccount/suppression Sub-Account key;
10. List Sub-Accounts — GET /api/v1/account/subaccount/ Account key;
11. Create Sub-Account — POST /api/v1/account/subaccount/ Account key;
12. Get Sub-Account — GET /api/v1/account/subaccount/{subaccount_id} Account key;
13. Get Aggregate Stats — GET /api/v1/account/subaccount/stat/{subaccount_id}/aggregate Account key;
14. List Daily Stats — GET /api/v1/account/subaccount/stat/{subaccount_id} Account key;
15. Get Message — GET /api/v1/account/message/{message_id} Account key;
16. List Webhooks — GET /api/v1/account/webhook Account key;
17. Create Webhook — POST /api/v1/account/webhook Account key;
18. Delete Webhook — DELETE /api/v1/account/webhook/{webhook_id} Account key;
19. List IPs — GET /api/v1/account/ip/ Account key;
Why Two Keys
This is the single most important thing to get right about SendPost. The API has two independent, non-interchangeable API keys, and the URL path tells you which one applies:
| Path prefix | Header | Key | Scope |
|---|---|---|---|
/api/v1/subaccount/... | X-SubAccount-ApiKey | Sub-Account API Key | One sending sub-account: send email, its sending domains, its suppression list |
/api/v1/account/... | X-Account-ApiKey | Account API Key | The whole account: create/list sub-accounts, statistics, message lookup, webhooks, IPs, IP pools |
Using the wrong key (or omitting the header) returns HTTP 401 Unauthorized. Each sub-account has its own key, so a multi-tenant setup normally installs the app once per sub-account with a different Sub-Account API Key, while the Account API Key stays the same across them.
Note the counter-intuitive part: the statistics for a sub-account live under /api/v1/account/subaccount/stat/{subaccount_id} and therefore need the Account key, not the sub-account key. Likewise Get Message is an account-level lookup.
API Notes
| Item | Value |
|---|---|
| Base URL | https://api.sendpost.io/api/v1 |
| Auth type | Static API key in a custom header (no OAuth, no token refresh, no expiry) |
| Content-Type | application/json for every write |
| Casing | Request and response bodies are camelCase |
| Timestamps | created, submittedAt etc. are UNIX epoch NANOseconds (19 digits), not seconds or milliseconds. Divide by 1,000,000,000 before feeding them to @formatDate(). |
| Date params | from / to on the stats and suppression endpoints are calendar dates, YYYY-MM-DD |
| Date range caps | Suppressions and daily stats: max 60 days. Aggregate stats: max 366 days. |
| Rate limits | Not documented by SendPost |
Status codes
| Code | Meaning |
|---|---|
| 200 / 201 | Success |
| 401 | Incorrect or missing API key header — you almost certainly used the account key where a sub-account key was needed, or vice versa |
| 403 | Resource already exists (duplicate domain, duplicate sub-account name, duplicate webhook URL) |
| 406 | Missing or non-existent resource id |
| 422 | Malformed request body |
| 500 / 503 | SendPost server error / maintenance |
Known quirks handled in these blueprints
- Send Email returns an ARRAY, one element per recipient, even for a single recipient. Flows cannot index into arrays, so both send endpoints loop the response with a ForEach and accumulate it with
@arrayAddinto aResultsarray, while also lifting the single recipient'smessageIdinto a flatMessageIDoutput. - Delete Suppressions is a DELETE with a JSON body. This is the one place the house rule "no Content-Type and empty payload on GET/DELETE" is deliberately broken, because SendPost requires the
{"suppressions":[...]}body. Delete Domain and Delete Webhook follow the normal rule (no body, no Content-Type). - Add Suppression keys the body by suppression type:
{"manual":[{"email":"..."}]}. Thetypeparameter is interpolated into the JSON key position rather than branching into four near-identical Call API stages. - HTML bodies are interpolated into a JSON payload. Keep
htmlBodyon a single line and use single quotes for HTML attributes (<a href='https://...'>). A raw double quote or a newline insidehtmlBodyorsubjectbreaks the JSON payload and SendPost answers 422. limit/offset/searchon the list endpoints are hardcoded tolimit=100&offset=0rather than exposed as parameters, because{{var||default}}syntax is not supported in a Call API URL.
Parameter Descriptions
| Endpoint | Parameter | Required | Notes |
|---|---|---|---|
| Send Email | fromEmail | Yes | Sender address. Its domain must be a verified sending domain on the sub-account, otherwise the send is dropped. |
| Send Email | fromName | Yes | Sender display name, e.g. Acme Support. |
| Send Email | toEmail | Yes | Single recipient address. |
| Send Email | subject | Yes | Subject line. Avoid raw double quotes. |
| Send Email | htmlBody | Yes | HTML body, single line, single-quoted attributes. |
| Send Email | trackOpens | No (default true) | Literal true or false. |
| Send Email | trackClicks | No (default true) | Literal true or false. Click tracking also needs the track CNAME verified on the domain. |
| Send Email With Template | templateName | Yes | Name of the template as saved in the SendPost dashboard, e.g. Welcome Mail. |
| Send Email With Template | templateVariables | No (default {}) | Raw JSON object of merge values, e.g. {"firstName":"Gavin"}. Must be valid JSON or SendPost answers 422. |
| Create Domain / Get / Delete Domain | name / domainId | Yes | name is a bare domain like mail.acme.com. domainId is the numeric id from List Domains. |
| List Suppressions | from, to | Yes | YYYY-MM-DD. Max 60 days apart. |
| Add Suppression | email | Yes | Address to suppress. |
| Add Suppression | type | Yes (default manual) | One of manual, unsubscribe, hardBounce, spamComplaint. Anything else silently suppresses nothing. |
| Remove Suppression | email | Yes | Address to un-suppress so it can receive mail again. |
| Create Sub-Account | name | Yes | Display name. Duplicate names return 403. |
| Get Sub-Account / stats | subAccountId | Yes | Numeric sub-account id from List Sub-Accounts. |
| Get Aggregate Stats | from, to | Yes | YYYY-MM-DD, max 366 days apart. |
| List Daily Stats | from, to | Yes | YYYY-MM-DD, max 60 days apart. |
| Get Message | messageId | Yes | The messageId UUID returned by Send Email. |
| Create Webhook | url | Yes | HTTPS endpoint that will receive events. Subscribes to all standard delivery and engagement events; fine-tune per event in the SendPost dashboard afterwards. Duplicate URL returns 403. |
| Delete Webhook | webhookId | Yes | Numeric webhook id from List Webhooks. |
Response Descriptions
| Endpoint | Key outputs |
|---|---|
| Send Email / Send Email With Template | MessageID (UUID — feed it to Get Message), To, SubmittedAt, RecipientCount, Results (array of {To, MessageID, SubmittedAt}) |
| List Domains | DomainCount, Domains array of {DomainID, Name, Verified, DkimVerified, ReturnPathVerified, TrackVerified, DmarcVerified, Created} |
| Create Domain / Get Domain | DomainID, Name, Verified plus the DNS records to publish: DkimHost/DkimValue (TXT), ReturnPathHost/ReturnPathValue (CNAME), TrackHost/TrackValue (CNAME), DmarcHost/DmarcValue (TXT) |
| List Suppressions / Add Suppression | Suppressions array of {SuppressionID, Email, ReasonCode, Created}. ReasonCode: 0 manual, 1 unsubscribe, 2 hard bounce, 3 spam complaint |
| Get Aggregate Stats | Processed, Delivered, Dropped, HardBounced, SoftBounced, Unsubscribed, Spam |
| List Daily Stats | DailyStats array of {Date, Processed, Delivered, Dropped, HardBounced, SoftBounced, Unsubscribed, Spam}, one object per day |
| List Sub-Accounts / Create Sub-Account | SubAccountID, Name, ApiKey — the ApiKey is that sub-account's Sub-Account API Key, i.e. what you paste into a new connection |
| Get Message | Full envelope of one sent message: MessageID, Subject, FromEmail, ToEmail, SubmittedAt, TrackOpens, TrackClicks, SubAccountID |
| List / Create / Delete Webhook | WebhookID, Url, Enabled, Created |
| List IPs | IPs array of {IPID, PublicIP, AutoWarmupEnabled, AutoWarmupStage, Created} |
How to Connect
- Create a SendPost account at https://app.sendpost.io/register.
- In the dashboard open the sub-account you want to send from and copy its API key. Paste it into the
Sub-Account API Keyconnection parameter. - Only if you want the account-level endpoints (stats, sub-accounts, webhooks, IPs, message lookup): open Account Settings → API Keys, copy the account key and paste it into
Account API Key. Leave it blank otherwise. - Add and verify a sending domain before you send anything — see below.
How to Test
Step 1 — verify the connection. Run List Domains with no parameters. A 200 with an empty Domains array is a valid pass: it proves the Sub-Account API Key is accepted. A 401 means the key is wrong or you pasted the account key.
Step 2 — add a sending domain. Run Create Domain with name = a domain you control, e.g. mail.acme.com. Publish the returned records in your DNS:
DkimHost→ TXT →DkimValueReturnPathHost→ CNAME →ReturnPathValueTrackHost→ CNAME →TrackValue(needed for click tracking)DmarcHost→ TXT →DmarcValue
Re-run Get Domain with the returned DomainID until Verified is true. DNS propagation is usually minutes but can take hours.
Step 3 — send. Run Send Email:
fromEmail:[email protected](must be on the verified domain)fromName:Acme SupporttoEmail: your own inboxsubject:SendPost test from FlowshtmlBody:<p>Hello from <b>Flows</b> - <a href='https://flows.world'>click me</a></p>trackOpens/trackClicks: leave empty (both default totrue)
Expect RecipientCount = 1 and a MessageID UUID. The mail should arrive within seconds.
Step 4 — inspect it. Paste that MessageID into Get Message (needs the Account API Key). You get the full envelope back.
Step 5 — stats. Run List Sub-Accounts to get your numeric SubAccountID, then Get Aggregate Stats with from = today minus 7 days, to = today (YYYY-MM-DD). Processed should include your test send. Note stats can lag a few minutes.
Step 6 — suppressions. Add Suppression with email = [email protected] and type left empty (defaults to manual), then List Suppressions with a from/to window covering today, then Remove Suppression for the same address.
Every endpoint carries a pink "How to test" note on its Designer canvas with concrete example values.
Use Cases
- Transactional email from any flow — password resets, receipts, shipping notices, OTP mails, alerting. Send Email is the workhorse; Send Email With Template keeps the copy in SendPost so marketing can edit it without touching the flow.
- Self-service tenant onboarding — Create Sub-Account returns that tenant's own API key, so a flow can provision an isolated sending identity per customer and store the key.
- Automated domain onboarding — Create Domain returns the four DNS records; a flow can push them straight into a DNS provider and then poll Get Domain until
Verifiedistrue. - Suppression hygiene — pipe unsubscribes and complaints from your CRM into Add Suppression, and honour re-consent with Remove Suppression.
- Deliverability monitoring — schedule Get Aggregate Stats daily and raise an alert when the hard-bounce or spam ratio crosses a threshold; List Daily Stats gives the per-day series for a chart or digest.
- Event ingestion — Create Webhook points SendPost at a Flows webhook trigger, turning deliveries, opens, clicks, bounces and complaints into flow runs.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 on every endpoint | Wrong key for the path | /subaccount/... needs Sub-Account API Key, /account/... needs Account API Key. They are not interchangeable. |
| 401 only on stats / Get Message / webhooks | Account API Key left blank | It is optional at connection time but mandatory for these endpoints. |
| Send returns 200 but no mail arrives | Sending domain not verified, or the recipient is suppressed | Check Verified via Get Domain; check List Suppressions for the recipient. A dropped send shows up under Dropped in stats. |
| 422 on Send Email | Broken JSON payload | A raw double quote or newline in subject / htmlBody. Use single quotes in HTML attributes and keep the body on one line. |
| 422 on Send Email With Template | templateVariables is not valid JSON | It is injected raw. Send {"firstName":"Gavin"}, not firstName=Gavin. Leave empty for {}. |
| 403 on Create Domain / Create Sub-Account / Create Webhook | Resource already exists | Duplicate domain name, sub-account name or webhook URL. List first. |
| 406 | Missing or non-existent id | Check domainId / subAccountId / webhookId came from the matching list endpoint. |
| Empty stats | Wrong date format or range too wide | YYYY-MM-DD; max 60 days for daily stats and suppressions, 366 for aggregate. |
| Timestamps look absurd | They are UNIX nanoseconds | Divide by 1,000,000,000 before formatting. |
| Click tracking not working | track CNAME not verified | TrackVerified must be true on the domain. |