Thanks for your interest! This is an open-source EmDash plugin. PRs and issues are welcome.
git clone https://github.com/drudge/emdash-plugin-postmark
cd emdash-plugin-postmark
pnpm install
# Develop with watch builds
pnpm dev
# Run the test suite
pnpm test
# With coverage (must pass thresholds: 80/75/80/80)
pnpm test:coverage
# Typecheck
pnpm typecheck
# Build
pnpm buildsrc/
├── index.ts # Descriptor factory (Vite build-time)
├── sandbox-entry.ts # definePlugin() (request-time)
├── postmark.ts # Thin REST client over fetch
├── config.ts # KV + env settings resolver
├── deliver.ts # email:deliver hook
├── admin.ts # Block Kit admin handler
├── webhook.ts # Postmark webhook handler
├── pickers.ts # Cached live fetchers for streams + signatures
├── logs.ts # Delivery log storage
├── retry.ts # withRetry helper
└── constants.ts # PLUGIN_ID / PLUGIN_VERSION
tests/
├── helpers/mockContext.ts # In-memory PluginContext mock
└── *.test.ts # Per-module vitest suites
The descriptor (src/index.ts) and sandbox entry (src/sandbox-entry.ts) are deliberately tiny — they wire imports into the EmDash plugin shape. All real logic lives in src/*.ts modules with their own unit tests.
- TypeScript strict mode;
noUncheckedIndexedAccessis on. Always handleundefinedfrom array/Map access. - No Node built-ins (
fs,path,node:crypto, etc.). The plugin must run in Cloudflare Workers / V8 isolates. - Use Web APIs (
fetch,Web Crypto,URL) for everything platform-related. - Comments are reserved for non-obvious why. Code should otherwise be self-explanatory.
- Use
vitest(pnpm test). - Mock the plugin context with
tests/helpers/mockContext.ts. - All public exports of every
src/*.tsmodule should have at least one test. - Coverage thresholds: 80% statements / 80% lines / 80% functions / 75% branches. CI fails below.
- Avoid touching the network. The Postmark client receives an injected
fetchso tests can mock it.
- Open an issue first if the change is non-trivial — it saves churn.
- Fork → branch → small focused commits.
pnpm typecheck && pnpm test:coveragemust pass.- Update
CHANGELOG.mdunder## [Unreleased]. - Open a PR. Squash-merge unless there's a reason not to.
- Bump the version in
package.jsonandsrc/constants.ts(they must match). - Move the
## [Unreleased]entries inCHANGELOG.mdunder a new dated heading. - Tag the release:
git tag vX.Y.Z && git push --tags. - Publish:
pnpm publish(CI optionally automates this on tag push — see.github/workflows/).
Be respectful and constructive. Project discussions are public; please assume good intent.