---
title: "SendPost App"
url: "https://help.flows.world/en/article/sendpost-app"
language: "en"
last_updated: "2026-09-19T11:07:03.565Z"
---

# 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 `@arrayAdd` into a `Results` array, while also lifting the single recipient's `messageId` into a flat `MessageID` output.
-   **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":"..."}]}`. The `type` parameter 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 `htmlBody` on a single line and use **single quotes** for HTML attributes (`<a href='https://...'>`). A raw double quote or a newline inside `htmlBody` or `subject` breaks the JSON payload and SendPost answers 422.
-   `limit` / `offset` / `search` on the list endpoints are hardcoded to `limit=100&offset=0` rather 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

1.  Create a SendPost account at https://app.sendpost.io/register.
2.  In the dashboard open the sub-account you want to send from and copy its **API key**. Paste it into the `Sub-Account API Key` connection parameter.
3.  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.
4.  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 → `DkimValue`
-   `ReturnPathHost` → CNAME → `ReturnPathValue`
-   `TrackHost` → 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`: `noreply@mail.acme.com` (must be on the verified domain)
-   `fromName`: `Acme Support`
-   `toEmail`: your own inbox
-   `subject`: `SendPost test from Flows`
-   `htmlBody`: `<p>Hello from <b>Flows</b> - <a href='https://flows.world'>click me</a></p>`
-   `trackOpens` / `trackClicks`: leave empty (both default to `true`)

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` = `blocked@example.com` 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 `Verified` is `true`.
-   **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. |
