npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

emdash-plugin-postmark

v0.2.0

Published

Send EmDash CMS emails through Postmark — settings, delivery log, webhook tracking, live stream/sender pickers.

Readme

emdash-plugin-postmark

Send EmDash CMS emails through Postmark — settings parity with the official WordPress plugin, plus a delivery log, real-time webhook tracking (deliveries, bounces, opens, clicks), and live stream + sender pickers.

CI npm version License: MIT

Postmark Settings appears in the EmDash admin sidebar after install.

Features

| | | |---|---| | ✓ | Drop-in replacement for EmDash's email transport — registers as the exclusive email:deliver provider | | ✓ | Full settings parity with the official WordPress Postmark plugin (token, sender, stream, force-from, force-HTML, track opens / links, tags, metadata) | | ✓ | Environment-variable overrides compatible with WordPress (POSTMARK_API_KEY, POSTMARK_SENDER_ADDRESS, POSTMARK_STREAM_NAME) | | ✓ | Live picker — Default Stream dropdown populated from /message-streams | | ✓ | Live picker — Default Sender dropdown populated from /senders (with optional Account API Token) | | ✓ | Webhook receiver — secured with a per-installation secret, transitions deliveries through delivered → bounced → spam_complaint, counts opens & clicks | | ✓ | Test connection button validates the token without sending email | | ✓ | Send test email button sends to the signed-in admin | | ✓ | Delivery log with status badges + relative timestamps; queryable by messageId (used by the webhook receiver) | | ✓ | Source-aware Postmark Tag + Metadata — filter EmDash sends by source in the Postmark dashboard | | ✓ | Retry on 5xx, 429, and network errors with exponential backoff | | ✓ | Sandbox-compatible — no Node built-ins; network:request capability restricted to api.postmarkapp.com |

Install

pnpm add emdash-plugin-postmark
# or
npm install emdash-plugin-postmark

Register in your EmDash site's astro.config.mjs:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { postmarkPlugin } from "emdash-plugin-postmark";

export default defineConfig({
  integrations: [
    emdash({
      plugins: [postmarkPlugin()],
    }),
  ],
});

For a sandboxed install (Cloudflare Workers), use sandboxed: [postmarkPlugin()] instead. The plugin is identical in both modes.

Configure

Open the EmDash admin → Postmark Settings in the sidebar.

| Setting | KV key | Env var override | Notes | |---|---|---|---| | Server API Token | settings:serverToken | POSTMARK_API_KEY | Required. From Postmark → your server → API Tokens. | | Account API Token | settings:accountToken | POSTMARK_ACCOUNT_TOKEN | Optional. Enables the live sender-signature picker. From Postmark → Account → API Tokens. | | Default Sender | settings:defaultSender | POSTMARK_SENDER_ADDRESS | Required. Must be a verified sender signature. | | Force Default Sender | settings:forceSender | — | Always use the Default Sender regardless of caller-supplied from. | | Message Stream | settings:defaultStream | POSTMARK_STREAM_NAME | Defaults to outbound. Live-picker dropdown. | | Track Opens | settings:trackOpens | — | Maps to Postmark TrackOpens. | | Track Links | settings:trackLinks | — | None / HtmlOnly / HtmlAndText / TextOnly. | | Force HTML | settings:forceHtml | — | Wrap text-only messages in minimal HTML so links render in all clients. | | Default Tag | settings:defaultTag | — | Postmark Tag added to every send when "Auto-tag with source" is off. | | Auto-tag with Source | settings:autoTagSource | — | Postmark Tag = source identifier (system, plugin:newsletter, …). | | Include Source as Metadata | settings:metadataSource | — | Adds Metadata.source = "<source>" to every send. | | Retry on Transient Errors | settings:retryEnabled | — | 3 attempts with exponential backoff. Default on. |

Resolution order: KV → environment variable → built-in default. The admin UI labels each field with its source.

Activate as the email provider

After saving the token, click Make active provider. EmDash will start routing all transactional email — magic-links, invites, password resets, plugin emails — through Postmark.

Webhooks

The settings page displays a webhook URL like:

https://your-site.example.com/_emdash/api/plugins/postmark/webhook?key=<secret>

Paste it into Postmark → Servers → your server → Streams → your stream → Webhooks. Enable the Delivery, Bounce, Spam Complaint, Open, and Click events you care about.

What you get:

  • Delivery rows transition sent → delivered once Postmark accepts the message at the recipient MX.
  • Bounces and spam complaints update the row status with the bounce type and description.
  • Opens and clicks are aggregated on the row (opens, clicks, firstOpenAt, lastClickedUrl).

The Regenerate webhook secret button rotates the secret. The old URL stops working immediately — update Postmark's webhook config when you rotate.

Security note: Postmark webhooks are not signed (the documented options are URL-embedded Basic Auth or a shared secret in the URL). The plugin uses a 192-bit URL-safe random secret stored in state:webhookSecret. Bad keys get a 401 with no body. Treat the URL as a credential.

Usage from other plugins

Other plugins can send email via the standard EmDash email pipeline once Postmark is the active provider:

import { definePlugin } from "emdash";

export default definePlugin({
  capabilities: ["email:send"],
  hooks: {
    "content:afterPublish": async (event, ctx) => {
      await ctx.email!.send({
        to: "[email protected]",
        subject: `Just published: ${event.content.title}`,
        text: `${event.content.title} is live.`,
      });
    },
  },
});

If autoTagSource is on, the email lands in Postmark with Tag = "plugin:<your-id>" so you can slice metrics by sender.

Tags & metadata in Postmark

When Auto-tag with source is on, every email is tagged with its EmDash source:

| Source | Postmark Tag | |---|---| | EmDash core (magic links, invites, password resets) | system | | Plugin emails | plugin:<plugin-id> | | The "Send test email" button | plugin:test |

This lets you filter the Postmark Activity screen, Deliverability stats, and Outbound webhooks by source. Combine with Include Source as Metadata for richer Postmark searches.

Troubleshooting

| Postmark Error | Plugin behavior | Fix | |---|---|---| | 10 (Invalid token) | Non-transient, logged + thrown | Re-check the Server API Token. Make sure it's the server token, not the account token. | | 300 (Invalid sender) | Non-transient, logged + thrown | Add and verify the sender in Postmark → Sender Signatures, then update Default Sender. | | 406 (Inactive recipient) | Non-transient | Recipient is suppressed. Reactivate in Postmark → Suppressions. | | 100 (Maintenance) | Transient — retried | Postmark window — usually resolves automatically. | | HTTP 429 | Transient — retried | Rate-limited; the retry helper backs off. Increase your Postmark plan if persistent. | | HTTP 5xx | Transient — retried | Postmark service issue. The retry helper handles short outages. |

The Recent deliveries table on the settings page shows the last 25 attempts with status badges, source, stream, opens, and timestamps.

FAQ

Is the plugin sandbox-safe?

Yes. The plugin is shipped in EmDash's standard format (no Node built-ins). It declares only hooks.email-transport:register and network:request capabilities, with allowedHosts: ["api.postmarkapp.com"]. Sandboxed installs see a capability consent dialog listing exactly that.

Why two different tokens?

Postmark has two token types:

  • Server tokens — scoped to a single Postmark "server" (which is really an outbound mail stream). Used for sending email and managing message streams.
  • Account tokens — scoped to your whole Postmark account. Used to enumerate sender signatures (/senders).

The plugin needs a server token to send. The account token is optional — supply it only if you want the Default Sender dropdown populated automatically.

Will it work with multiple senders?

The plugin currently exposes one Default Sender. Per-source routing rules and multi-server support are on the roadmap.

Can I migrate from another email provider plugin?

Yes — EmDash's email:deliver hook is exclusive, so installing Postmark and clicking Make active provider swaps providers atomically. The previous provider's plugin can stay installed (it just stops receiving traffic).

What if Postmark is down?

The retry helper handles transient outages. For longer outages, you'll see failed rows in the delivery log. Email pipeline errors propagate to callers (e.g., the user-invite endpoint will fail), so the operator sees the issue.

Development

git clone https://github.com/drudge/emdash-plugin-postmark
cd emdash-plugin-postmark
pnpm install
pnpm test               # run vitest
pnpm test:coverage      # with coverage report
pnpm typecheck          # tsc --noEmit
pnpm build              # produce dist/ via tsdown

The plugin runs against emdash >= 0.7.0. Tests use vitest with mocked storage / KV / fetch — no live Postmark account required for CI.

To run the plugin against a local EmDash dev site, point a workspace path dependency at this repo's root and add postmarkPlugin() to your astro.config.mjs.

License

MIT © Nicholas Penree