# Migrate from Postmark (/migrate/postmark)



Sendly implements Postmark's `POST /email` and `POST /email/batch`. Keep the official
`postmark` (Node) SDK — change the **request host** and the **server token**.

* **Base URL:** `https://api.sendly.now/api/compat/postmark`
* **Auth:** `X-Postmark-Server-Token: <sk_...>` — Postmark's header auth style; the SDK
  sets this from the token you pass. Use a Sendly secret key as the token.

<Callout type="info">
  Postmark does not publish an official Python SDK. Python senders can use the Node SDK,
  call the compat endpoint over plain HTTP, or use the [Sendly Python SDK](/sdks).
</Callout>

## The two-variable swap [#the-two-variable-swap]

The Postmark Node client takes a `requestHost` (host **plus path**, without the scheme)
and a `useHttps` flag, so the dialect prefix hangs off the host:

```js title="Before (Postmark)"
import * as postmark from "postmark";

const client = new postmark.ServerClient("your-postmark-server-token");

await client.sendEmail({
  From: "you@yourdomain.com",
  To: "customer@example.com",
  Subject: "Hello",
  HtmlBody: "<p>Welcome aboard.</p>",
});
```

```js title="After (Sendly)"
import * as postmark from "postmark";

const client = new postmark.ServerClient("sk_your_sendly_key", {
  useHttps: true,
  requestHost: "api.sendly.now/api/compat/postmark",
});

await client.sendEmail({
  From: "you@yourdomain.com",
  To: "customer@example.com",
  Subject: "Hello",
  HtmlBody: "<p>Welcome aboard.</p>",
});
```

## Supported fields [#supported-fields]

| Field                                       | Support  | Notes                                                                    |
| ------------------------------------------- | -------- | ------------------------------------------------------------------------ |
| `From`                                      | Full     | Plain address or `"Name <you@domain.com>"` form.                         |
| `To` / `Cc` / `Bcc`                         | Full     | Comma-separated recipient lists, as Postmark sends them.                 |
| `Subject`                                   | Full     |                                                                          |
| `HtmlBody` / `TextBody`                     | Full     | `HtmlBody` is preferred when both are present. At least one is required. |
| `ReplyTo`                                   | Full     |                                                                          |
| `Headers`                                   | Full     | `[{ Name, Value }]`.                                                     |
| `Attachments`                               | Full     | `[{ Name, Content, ContentType, ContentID }]`.                           |
| `Tag`                                       | Partial  | Single tag, sanitized.                                                   |
| `MessageStream`, `TrackOpens`, `TrackLinks` | Accepted | Accepted and ignored.                                                    |
| `POST /email/batch`                         | Full     | Returns an array of results, order preserved.                            |

Success comes back in Postmark's shape:
`{ To, SubmittedAt, MessageID, ErrorCode: 0, Message }`.

## Not supported (returns a clean Postmark error) [#not-supported-returns-a-clean-postmark-error]

Template sends (`sendEmailWithTemplate` / `TemplateId` / `TemplateModel`) aren't
implemented in this phase. Anything Sendly can't map returns Postmark's
`{ ErrorCode, Message }` error body with the matching status (for example a bad token is
`401` with `ErrorCode: 10`), so the SDK's own error classes — `InvalidAPIKeyError`,
`ApiInputError`, and friends — are thrown exactly as they are today.
