Postmark

Lightweight, dependency-free, in-memory Postmark API fake for testing code that uses the real postmark Node SDK (or the language-agnostic Postmark REST API).

Default port: 4827

Quick start

Start the server:

import { PostmarkServer } from "./services/postmark/src/server.js";

const server = new PostmarkServer(4827);
await server.start();
// ... run your app/tests ...
await server.stop();

Point the real postmark client at it:

import { ServerClient } from "postmark";

const client = new ServerClient("parlel-server-token", {
  // The SDK is configured via Configuration; or fetch directly against the base URL:
});

await fetch("http://127.0.0.1:4827/email", {
  method: "POST",
  headers: {
    "X-Postmark-Server-Token": "parlel-server-token",
    "Content-Type": "application/json",
    Accept: "application/json",
  },
  body: JSON.stringify({
    From: "sender@parlel.dev",
    To: "recipient@parlel.dev",
    Subject: "Hello",
    HtmlBody: "<b>Hi</b>",
    TextBody: "Hi",
  }),
});
// => { To, SubmittedAt, MessageID, ErrorCode: 0, Message: "OK" }

Every send is captured and inspectable via /__parlel/*.

Access via MCP / preview URL

When run under the parlel pool, this service is reachable through the MCP gateway and a preview URL at http://127.0.0.1:4827. Use POSTMARK_BASE_URL to point clients/agents at it. Captured mail lives at GET /__parlel/messages so you can assert sends without delivering real email.

Implemented operations

All routes require the X-Postmark-Server-Token (or X-Postmark-Account-Token) header. JSON bodies use PascalCase fields. State is in-memory and ephemeral.

Service & inspection operations (parlel extensions)

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
sendEmail (POST /email)✅ Supported
sendEmailBatch (POST /email/batch)✅ Supported
sendEmailWithTemplate✅ Supported
Outbound message listing✅ Supported
Server info✅ Supported
Captured-mail inspection✅ Supported (parlel extension)
Actual email delivery / SMTP✓ By design — Captured in-memory for inspection — no real messages sent
Real template rendering◐ Accepted; not rendered
Bounces / stats / inbound / triggers⟳ Roadmap
Real token validity / scope enforcement✓ By design — Any non-empty credential is accepted — no real secrets needed
Rate limiting (429)✓ By design — Never throttles — local tests run at full speed, zero cost

Error shapes

Errors use the Postmark envelope { "ErrorCode": <n>, "Message": "..." }.

StatusErrorCodeWhen
40110missing server/account token
422300invalid From/To or malformed body
4221101invalid/missing template on withTemplate
404404unknown endpoint or captured message

Manifest

See services/postmark/manifest.json:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

POSTMARK_SERVER_TOKEN=parlel-server-token
POSTMARK_BASE_URL=http://parlel-bridge:4827
<!-- parlel:testenv:end -->