> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getdialed.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Transactional email

> Send transactional email through Postmark from a domain you control — with suppression surfaced honestly, sends paced by the dispatcher, and a reply that can pause the flow until a human answers

The `postmark__email` service sends **transactional email**: the notification, the receipt, the alert, the question a person is expected to answer. Two steps ship, both authored on the same shared REST foundation every other integration uses, and both authenticated by selecting a [credential](/concepts/credentials) rather than by pasting a key into a step.

```json theme={null}
{
  "task_id": "task_notify",
  "task_name": "Tell the owner the import finished",
  "platform_id": "postmark",
  "service_id": "postmark__email",
  "action_id": "postmark__email__send",
  "connection_id": "cred_e5f6g7h8",
  "parameters": {
    "from": "alerts@example.com",
    "from_name": "Acme Operations",
    "to": ["{{ input.owner_email }}"],
    "subject": "Your import finished",
    "text_body": "{{ input.record_count }} records were processed."
  }
}
```

<Note>
  **Marketing email is out of scope, deliberately.** There are no campaigns, no bulk template rendering and no open/click analytics surface here. A step sends one message to a named set of recipients; a flow that mails a list does it by running that step per record, which is what keeps one bad address from failing everybody else's mail.
</Note>

## The two steps

| Action                           | What it does                                                                                                                                                   |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `postmark__email__send`          | Sends one email and returns as soon as Postmark accepts it for delivery.                                                                                       |
| `postmark__email__send_and_wait` | Sends one email and then **pauses the flow** until the recipient replies or the step's timeout runs out. See [human in the loop](/concepts/human-in-the-loop). |

There is no third step for reading an attachment, and its absence is the design rather than a gap. When a reply arrives, its attachments are **stored at that moment** and the flow is handed a [file reference](/concepts/files) for each one — so there is nothing left to fetch. Read the file with `getdialed__files__read_text`, or hand its `file_id` straight to `getdialed__records__parse_csv`. The [send-and-wait guide](/guides/send-and-wait-for-a-reply) walks the whole chain.

### Sending

`postmark__email__send` takes recipients and a message:

| Parameter                 | Required | What it is                                                                                                                                                                                                             |
| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `to`                      | yes      | The recipient addresses. At least one, and **no more than 50**.                                                                                                                                                        |
| `from`                    | yes      | The sending address. It must be covered by a **confirmed sender signature** in your Postmark account, or belong to a **sending domain that account has verified**.                                                     |
| `from_name`               |          | The display name shown beside the sending address.                                                                                                                                                                     |
| `reply_to`                |          | Where replies should go, when that is not the sending address. The send-and-wait step sets this itself and overrides anything entered there, because the reply has to return to the address that step is listening on. |
| `cc` / `bcc`              |          | Additional recipients, visible and hidden respectively. **No more than 50 each.**                                                                                                                                      |
| `subject`                 |          | The subject line.                                                                                                                                                                                                      |
| `text_body` / `html_body` | one of   | The message body. Supply either or both — a send with neither is refused. Sending both lets the recipient's mail client choose.                                                                                        |

The 50-per-field cap is Postmark's, and a send that exceeds it is refused **before the request leaves** — so an over-large recipient list costs no send and no pacing budget. The whole message, including anything attached, may not exceed **10 MB**.

It reports:

| Output                 | What it holds                                                                                                                                                                      |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_message_id`  | Postmark's identifier for the accepted message. Use it to correlate the delivery events Postmark reports afterwards.                                                               |
| `status`               | Always `accepted`. Postmark answers a successful send with an acceptance rather than a delivery; delivery is reported later.                                                       |
| `recipient_count`      | How many addresses the send was directed at, counting `to`, `cc` and `bcc` together.                                                                                               |
| `partial_suppression`  | How many of those recipients were skipped because they are inactive, when the message was accepted for the rest. Empty on a clean send. See [suppression](#suppressed-recipients). |
| `message_unrecognized` | `true` when Postmark accepted the message and then said something about it this step could not interpret. Treat it as a send that needs checking.                                  |

<Note>
  **No output field can carry a recipient address, and that is structural rather than a convention.** Step outputs are persisted into the platform's run history, which outlives whatever retention rule governed the recipient list — an address there could never be redacted afterwards. So suppression is reported as **counts**, never as the addresses themselves, and there is no field on this step that could hold one. The same rule governs failure messages: a refusal names the reason and the count, never the address.

  Postmark itself is less careful — it echoes recipients back on a successful send and names skipped addresses in its own prose. Those are read here only to be counted, and the count is the only thing that leaves the step.
</Note>

## Connecting Postmark

The credential is a Postmark **server token** — not an account token. Create it as an [`api_key` credential](/concepts/credentials#postmark-server-token) whose key is presented as an `X-Postmark-Server-Token` header:

```json theme={null}
{
  "name": "Postmark Production",
  "platform_id": "postmark",
  "auth_method": "api_key",
  "api_key_placement": {"header_name": "X-Postmark-Server-Token"},
  "credentials": {"server_token": "REPLACE_ME"}
}
```

GetDialed calls Postmark with the token, reads back which **Server** it belongs to, and files the credential under that server — you never look up or type a provider identifier. See [platform tenancy](/concepts/platform-tenancy) for what that record owns.

<Warning>
  **One credential is one Postmark server, and the boundary matters more here than it looks.** A server token can read that server's own message history, including message content Postmark retains after delivery. So the server is the isolation boundary, not just the budget key: one server per account is what keeps one customer's mail out of another's reach. A token that covered several accounts' mail would be a content leak rather than a shared allowance.
</Warning>

## Where mail is sent from

Postmark checks that you are entitled to the `from` address on every send, in one of two ways.

### Sender signature

A **sender signature** is a single address Postmark has confirmed — it emails the address and somebody clicks the link. Nothing to publish in DNS, and you can send the first message minutes after confirming it. It is the right choice for trying the integration out and for internal notifications from one fixed address.

A signature covers **that address only**. A flow that sends from several addresses on the same domain wants a verified domain instead.

### Verified sending domain

Verify the whole domain and any address on it can send. Postmark generates the DNS records; you publish them and then ask Postmark to verify. Two matter for sending:

| Type    | Host                             | Purpose                             | Required?    |
| ------- | -------------------------------- | ----------------------------------- | ------------ |
| `TXT`   | `<selector>._domainkey.<domain>` | DKIM — signs your outbound mail     | **Required** |
| `CNAME` | the Return-Path host             | Aligns the bounce domain with yours | Recommended  |

Publish an SPF and a DMARC record for the domain too. Neither is Postmark-specific and neither is generated for you, but a domain sending real mail without them will land in spam folders that a verified domain would not.

### Receiving replies

**Reply-waiting steps additionally need an inbound domain**, and it is a separate piece of configuration from the sending one:

* The domain is bound as an **inbound domain on the server** your credential belongs to.
* Its `MX` record points at `inbound.postmarkapp.com`.

<Warning>
  **That `MX` record takes over the domain's mail. Use a dedicated subdomain — never an apex that already receives mail.**

  Publishing it on a domain that already receives mail — your company's main domain, the one your staff read their inbox on — hands that mail to Postmark and stops it reaching your existing mail provider. This is the single most expensive mistake available on this page and it is not reversible in the minutes it takes to notice.

  Create a subdomain for the purpose (`reply.example.com`, `mail.example.com`) and bind **that**. A subdomain has no existing mail to lose, and it keeps your reply address obviously distinct from a human mailbox.
</Warning>

You only need an inbound domain if you intend to use [send and wait for a reply](/concepts/human-in-the-loop). A send-only setup needs no `MX` change at all.

<Note>
  **An inbound domain binds to exactly one server.** That is a Postmark constraint, and it is worth knowing before you plan around it: two servers cannot share one reply domain, so a second server needs its own subdomain.
</Note>

## Suppressed recipients

Postmark maintains a suppression list per server: addresses that hard-bounced or reported spam are marked **inactive**, and Postmark will not deliver to them. **GetDialed exposes no way to bypass that.** There is no parameter, no flag and no override — that is the design, not a missing feature.

What GetDialed adds is that you always find out. Suppression arrives in three shapes and **none of them is reported as a clean success**:

| What happened                                               | What the step does                                                                                                                                                                                                                      |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No recipient was suppressed                                 | Succeeds. `partial_suppression` is empty.                                                                                                                                                                                               |
| **Some** recipients were suppressed, the rest were accepted | Succeeds, with `partial_suppression` carrying **how many** were skipped. This is explicitly **not** a clean success — branch on the count if a partial delivery matters to you, and an operator sees it logged as a warning either way. |
| **Every** recipient was suppressed                          | **Fails** with a distinct named error, `RECIPIENT_SUPPRESSED`, naming the count.                                                                                                                                                        |

That last row is the one worth internalising, and Postmark makes it easy to miss. It answers a partially suppressed send with a **`200`** and its own success error code — the same status line as a perfect send — and names the skipped addresses only in a free-text sentence. Reported as success it would look identical to a fully delivered message everywhere downstream, so the count is extracted and reported instead.

The same is true of a **fully** suppressed send: Postmark may answer it with a success status line and an empty delivery, or with a `422`. Both are the same outcome — nothing was delivered — so both fail under the same name, rather than one being a failure and the other a success with a suspicious count.

<Note>
  **A count, not a reason list.** Postmark publishes no reason vocabulary for a skipped recipient — it reports the fact in prose and nothing else — so this step carries no `suppressed_reasons` field. An always-empty reasons field would read as "no suppression happened", which is exactly the misreading the count exists to prevent.

  Because that prose is the entire detection mechanism and it is Postmark's to reword at any time, a message this step cannot interpret sets `message_unrecognized` and logs a warning rather than passing as clean. A rewording is then visible in your flow, instead of turning every mixed send into a silent success.
</Note>

To change any of this, review the inactive recipients on your own Postmark server. Nothing in a flow can reach past it.

## Pacing, and sending exactly once

**Sends are paced, and the pacing is the dispatcher's job rather than the step's.** Every email call draws from the `Email` rate bucket on your Postmark [Server record](/concepts/platform-tenancy#rate-buckets-and-effective-limits), spends it in the `interactive` [lane](/concepts/dispatch-pacing#lane-order-and-the-starvation-floor), and waits rather than failing when the budget is spent. A transactional email is user-visible latency — somebody is waiting on the password reset — which is why it sits in the lane reserved for work a person is watching, and not behind a bulk record load.

<Note>
  **The published cap for email is ours, not Postmark's.** Postmark's rate-limit response headers describe API request throughput per second, not the mail-volume budget this cap governs, so they are not used as send limits. The catalog ships a deliberately conservative GetDialed default that errs low: too low costs latency, while too high costs throttling and a reputation hit on a sending domain, which no later correction undoes. The value is corrected from observed pacing behaviour and written onto the [Server record](/concepts/platform-tenancy), the same way every other provider cap is.
</Note>

<Warning>
  **A send is never retried automatically.** Both send steps declare `retry_safety: at_most_once` in the [catalog](/concepts/catalog#what-each-level-tells-you), which routes them to a single-attempt policy: if the answer to a send is lost in transit, the platform does **not** send again to find out. A duplicate email cannot be recalled, and for a send-and-wait a duplicate would be worse than an extra message — it would create a second reply address for a wait that only one of them can resume, so the human could answer the copy nothing is listening on.

  **Postmark publishes no idempotency key**, so unlike some providers there is no second line of defence at the provider's end. The single-attempt declaration is the whole of the guarantee.
</Warning>

## Quiet hours do not apply to email

**Email carries no send-time restriction here, and that is a deliberate position rather than a gap.** Quiet-hours rules are an obligation on calls and text messages; email sits under different rules that impose no time-of-day restriction on sending.

So there is no quiet-hours gate on an email step, and none is coming for email. If your own policy says a particular flow should not mail people overnight, express it with the [`windowed` execution model](/concepts/execution-models#windowed), which confines a task to a recurring local-time window and will wait until the window next opens.

## Next steps

<CardGroup cols={2}>
  <Card title="Human in the loop" icon="user-check" href="/concepts/human-in-the-loop">
    Pause a flow until a person replies, and what lands in context when they do.
  </Card>

  <Card title="Send and wait for a reply" icon="reply" href="/guides/send-and-wait-for-a-reply">
    The end-to-end walkthrough: ask for a CSV, wait, parse it, fan out.
  </Card>

  <Card title="Credentials" icon="key" href="/concepts/credentials#postmark-server-token">
    Create the Postmark credential these steps select.
  </Card>

  <Card title="Dispatch pacing" icon="gauge-high" href="/concepts/dispatch-pacing">
    How sends are paced against your server's budget, and what a pause holds.
  </Card>
</CardGroup>
