Building an Email Provider Plugin for emdash
April 4, 2026. We built a Postmark email provider plugin for emdash, and in the process found a framework bug that affects every plugin that sends email.
The goal
emdash has a forms plugin that collects submissions and sends email notifications. But it needs an email provider to actually deliver the messages. In dev mode, emdash has a console provider that logs emails to stdout. In production, there's nothing. You need to bring your own.
We use Postmark for transactional email, so we built a plugin that hooks into emdash's email:deliver pipeline and sends via the Postmark API.
What we learned about the plugin system
emdash's plugin system has two parts: a descriptor (build-time metadata) and a definition (runtime logic via definePlugin()). The descriptor goes in astro.config.mjs. The definition exports a createPlugin function that returns hooks, routes, and storage config.
The critical lesson: your plugin must use definePlugin() from emdash. We initially tried returning a plain object with the right shape, but the hook normalization that definePlugin performs (adding pluginId, priority, timeout to each hook) is required for the pipeline to dispatch correctly. Without it, the hook registers but never fires.
For a local plugin that lives inside the site directory, you can import definePlugin directly. If your plugin is in an external package, list emdash as a peer dependency.
The bug we found
Even with definePlugin, email notifications weren't sending. After hours of debugging (patching the built Worker output with console.log statements and deploying repeatedly), we traced the issue to emdash's PluginRouteRegistry.
When a plugin route handler runs (like the forms submit endpoint), emdash creates a new PluginContextFactory with only { db } as options. The emailPipeline is never passed. So ctx.email is always undefined in plugin route handlers, and the forms plugin silently skips sending notifications.
We filed this as emdash-cms/emdash#215 and wrote a post-build patch that adds emailPipeline to the route registry construction. It runs automatically as part of our deploy script.
Why this matters
Every bug we find and document in emdash is knowledge we can reuse. When we deploy the next emdash site for a client, we already know about the paid plan requirement, the publish two-step, the email pipeline bug. That's the value of running a real site on a beta CMS: you build institutional knowledge that saves time on every future project.
We're also open-sourcing the Postmark plugin once it's cleaned up. It's a small piece of code, but it's a working example of an emdash email provider that anyone deploying on Cloudflare can use.
Comments
No comments yet. Be the first to share your thoughts.
Leave a comment