Moving from Postmark

Move an app built for Postmark to Mailroomly without rewriting it: it keeps its requests and its webhook handlers, and you change the address it sends to and its token.

What stays the same

Mailroomly speaks Postmark's sending API and its webhook formats:

  • The endpoints. POST /email sends one email and POST /email/batch up to 500, at the root of https://app.mailroomly.com (not under /api/v1). They take Postmark's request (From, To, Cc, Bcc, Subject, HtmlBody, TextBody, ReplyTo, Tag, Metadata, Headers, Attachments, TrackOpens, TrackLinks, MessageStream) and answer in Postmark's shape, with ErrorCode 0 and a MessageID on success.
  • The token header. An app's API key goes in X-Postmark-Server-Token, where the Postmark server token was. Authorization: Bearer works too.
  • Message streams. A MessageStream with broadcast in its name (broadcast, broadcasts, my-broadcast-stream) sends marketing email; anything else, or none, sends transactional email.
  • Unsubscribe links. In marketing email, {{{ pm:unsubscribe }}} becomes each recipient's own unsubscribe link, and every copy carries one-click unsubscribe headers.
  • Webhooks. With the Postmark-compatible format, events arrive as Postmark's records: Delivery, Bounce, SpamComplaint, Open, Click, SubscriptionChange, and Postmark's inbound JSON for received email, attachments included.

Every field and record: Postmark compatibility.

Before you start

  • A verified domain. The app's sending domain must be verified in Mailroomly (Your own domain). Mailroomly's DNS records and Postmark's sit side by side, so both can send for the same domain while you move.
  • A daily limit that fits. A new account sends to a limited number of recipients per day until it becomes a trusted account. Check Plan before moving a busy app, and ask for a higher limit if you need one (Sending trust).
  • Your blocked addresses. Postmark's inactive addresses (hard bounces, spam complaints, unsubscribes) don't move by themselves. Export them from Postmark and block them in Activity › Blocked › Block an address, or through the API, before the first send (Blocked). Block hard bounces and complaints for every type of email, and unsubscribes for marketing.
  • Templates. Mailroomly has no templates: an app that sends with TemplateId or TemplateAlias renders its HTML and text before sending.

Move an app over

Apps move one at a time. Apps › Move over from Postmark (also in Ctrl K) walks you through the three steps in the web app: connect an app in Postmark's format, point your code at Mailroomly, and move your webhooks.

1. Create the app and its key

In Move over from Postmark, choose Connect an app under Connect an app in Postmark’s format. Give the App name and choose What it does (Sends email is enough for most apps), then Which emails does this key reach?, then Create the app and its key. The app's webhooks already use the Postmark-compatible format.

The key, starting with mp_live_, takes the place of your Postmark server token. It is shown only once: copy it into the app's secret store. To narrow it to the app's domains or to one type of email, create another key on the app's page and revoke the first (Apps and keys).

2. Point your code at Mailroomly

Change two values in the app's configuration: the address it sends to and the token. The request stays the same.

# Before
curl https://api.postmarkapp.com/email \
  -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{"From": "…", "To": "…", "Subject": "…", "TextBody": "…", "MessageStream": "outbound"}'

# After
curl https://app.mailroomly.com/email \
  -H "X-Postmark-Server-Token: $MAILROOMLY_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{"From": "…", "To": "…", "Subject": "…", "TextBody": "…", "MessageStream": "outbound"}'

With a Postmark library that lets you change its base address, set it to https://app.mailroomly.com. Batches go to /email/batch on the same address.

Check each MessageStream the app uses: a transactional stream whose name contains broadcast, or a marketing stream whose name doesn't, must be renamed in the app.

3. Move your webhooks

On the app's page, open the Webhook step:

  1. Webhook URL: the endpoint that received Postmark's webhooks.
  2. Payload format: Postmark-compatible (already set by the guide).
  3. Events to send: tick what the endpoint handles: Delivered, Bounced, Complaint, Opened, Clicked, Unsubscribed, Delivery delayed and Inbound message.
  4. Save changes, then Send a test event to see what your endpoint answers.

One URL receives every event the app chooses; Postmark allowed a URL per stream and type. The URL can't carry a user name and password: if your endpoint relied on them, check the signature Mailroomly sends with every webhook instead (Webhooks).

4. Receive email, if the app did with Postmark

For an app that received email through Postmark's inbound webhook:

  1. Set the domain to Sends and receives (What this domain does, in its Settings), usually the same subdomain the app used with Postmark, never a domain whose mail lives elsewhere (Addresses and domains).
  2. Create the receiving address, choose the app as its Owning app and give it an Inbound webhook URL: the app's current inbound endpoint. Requests arrive in Postmark's inbound JSON, signed with the app's secret.
  3. If the app used plus-addressing (reply+ticket42@…), make that address the domain's Catch-all address: the part after + arrives in MailboxHash.
  4. Change the domain's MX record to the value on the domain's page (DNS records), and wait until the page shows it in place. Lower the record's time to live a day ahead, so you can switch back quickly.

Setting an address's owning app or inbound webhook URL needs a person signed in to Mailroomly: an agent can't do it (AI agents).

5. Test

Send one message from the app. The answer has ErrorCode 0 and a MessageID, and the email shows in Activity, going from Sent to Delivered. Send a test as this app, on the app's page, sends one to you and lists the events your webhook received (Send a test email). For receiving, send an email to the address: it appears in the inbox, and Webhook deliveries shows the delivery to your endpoint.

6. Watch Activity

For the first days, keep an eye on:

  • Activity: every send, with its route and events (A send's page). Blocked shows the addresses Mailroomly doesn't send to, and why.
  • Webhook deliveries: deliveries your endpoint did not accept, to fix and replay.
  • Notices: bounces, complaints, daily limits and failed webhooks, each saying what to do.

Switching back

Keep the Postmark server, its DNS records and its webhooks until the move is done. To go back, restore the two values, the address and the token: the app sends through Postmark again within minutes. For receiving, point the MX record back to Postmark. Email already accepted by Mailroomly is still sent, and stays in Activity.

What differs

In Mailroomly
Templates Not supported: render HTML and text in the app before sending.
Postmark's other APIs Bounces, message search, statistics, servers, streams, suppressions, sender signatures and domains are not emulated. Use the REST API and the web app (Activity, Blocked, Addresses).
Senders Any address on a verified domain sends; there are no single-address sender signatures.
Repeat-safe sends Not on /email. To make retries safe, send through POST /api/v1/messages with an idempotencyKey.
Ids MessageID and the webhook ID are UUID strings. The MessageID is the message's id in Mailroomly's own API and webhooks.
Blocked recipients Left out of a message that still has other recipients. A message whose recipients are all blocked gets ErrorCode 406.
Limits 50 recipients and 10 MB per message, 500 messages per batch, 100 requests per second per key.
Refusals The daily limit, or the key's own, is ErrorCode 405 until midnight UTC, and so is a plan's month of emails used up; a paused type of email, a key outside its scope or marketing that isn't allowed is 412; an unverified domain is 400; a bad or missing key is 10.
Delivery delays Arrive as a Bounce record with Type: "Transient".
Webhook fields No ServerID, BounceID, Geo, Client, OS, Platform, ReadSeconds or ClickLocation.
Webhook retries Failed deliveries are retried for about 45 hours, then marked Dead and can be replayed (Webhooks). Each attempt must finish within 10 seconds: answer 2xx at once and do the work afterward.
Signature Every webhook carries X-Messaging-Signature. Checking it is optional for a moved app, and recommended.

The developer reference, field by field: Postmark compatibility.