a13n
Build AI agents and run them on cloud.
A small, fast daemon that lets every app on a host or in a pod send mail through Postmark over HTTP, SMTP, sendmail or a CLI. Single static Go binary, systemd native, container ready.
Translation pending
Developer toolsGo · CSSGitHub @rossedmanOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
postmarkd is a small daemon that runs next to your apps (as a systemd service or a container sidecar) and delivers mail through Postmark
Taken from the repository README
Loading the full README.
Read the full README ↗Sending mail should be one line. postmarkd is a small daemon that runs next
to your apps (as a systemd service or a container sidecar) and delivers mail
through Postmark. Apps hand it mail however they
like: HTTP, SMTP, the sendmail command or a CLI. postmarkd holds the
credentials, fills in defaults, sends in parallel and retries safely.
echo "The nightly backup finished." | postmarkd send -to [email protected] -subject "Backup OK"
| From | How |
|---|---|
| Shell scripts | postmarkd send -to … -subject … (body from stdin or -text/-html, -attach files) |
Anything that uses sendmail: cron, mail/mailx, PHP mail(), git, logwatch, smartd | Symlink postmarkd as /usr/sbin/sendmail. No other change needed. |
| Apps that speak SMTP: Rails, Django, Laravel, WordPress, Grafana, Gitea… | Set POSTMARKD_SMTP_LISTEN=127.0.0.1:2525 (on by default in the container) and point them at it. No username, password or TLS needed. |
| Apps with a Postmark SDK | Change the SDK's base URL to postmarkd |
| Any HTTP client | curl --unix-socket /run/postmarkd/postmarkd.sock http://localhost/email -d '{…}' |
All of them end up in the same place: validated, defaulted, rate-limited and sent to Postmark over pooled HTTPS connections.
Postmark already has a good API, SMTP and SDKs, and for a single app that already uses a Postmark SDK, calling Postmark directly is fine. postmarkd helps when mail comes from many places, or from places that can't easily talk to Postmark.
| Postmark directly | With postmarkd | |
|---|---|---|
| Server token | Copied into every app, container and CI secret. Rotating it means touching all of them. | Lives in one place (a systemd credential or one Secret). Apps get nothing, or their own client token that you can revoke alone. |
System mail (cron, mail, PHP mail(), git, monitoring tools) | Needs a local MTA such as Postfix set up as an authenticated TLS relay. | Symlink as sendmail. Done. |
| Legacy or SMTP-only apps | Must be configured for Postmark's SMTP host, TLS and credentials. | localhost:2525, no credentials, no TLS. |
| Shell scripts | Hand-written curl with the token and JSON escaping. | echo body | postmarkd send -to … -subject … |
| Defaults and rules (From address, message stream, validation) | Repeated in each app. | Set once for every app on the host. |
| Tests and CI | Postmark's test token or blackhole address. Still needs network and an account, and you can't easily read back what was sent. | POSTMARKD_BLACKHOLE=true: no account, no network, and tests can read what was "sent". |
| Request latency | Every send waits on a round trip to Postmark. | Over a local socket, or ?async=true returns immediately while postmarkd sends in the background. |
| Load on Postmark | Each app opens its own connections and does its own retries. | Shared keep-alive connections, one concurrency limit, retries only when safe (no duplicate mail). |
| Visibility | Postmark's activity page. | The same, plus Prometheus metrics for every app on the host. |
| DNS setup | Postmark's dashboard. | postmarkd check-domain checks DKIM, Return-Path and DMARC from the command line or CI. |
It's one more component to run, but a small one: a single static binary with no dependencies and no state on disk.
sendmail or the CLI.POSTMARKD_MAX_CONCURRENCY requests to Postmark at once over pooled
keep-alive connections, and an optional bounded async queue with back-pressure.MessageStream, the
X-PM-Message-Stream header, -stream, or ?stream= per request), so
transactional and broadcast mail flow side by side, even in one batch.POSTMARKD_BLACKHOLE=true accepts everything,
answers exactly like Postmark, sends nothing, and lets your tests read back
what was "sent" at /blackhole/messages.postmarkd check-domain example.com tells
you which DNS records are missing so your mail doesn't land in spam.Type=notify, watchdog, graceful drain, hardened unit./metrics, /healthz, /readyz.Install the package for your distribution (amd64 or arm64):
# Debian / Ubuntu
VERSION=0.1.1 ARCH=$(dpkg --print-architecture)
REL=https://github.com/rossedman/postmarkd/releases/download/v$VERSION
curl -fsSLO $REL/postmarkd_${VERSION}_linux_$ARCH.deb
sudo apt install ./postmarkd_${VERSION}_linux_$ARCH.deb
# Fedora / RHEL / Rocky / Amazon Linux
VERSION=0.1.1 ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
REL=https://github.com/rossedman/postmarkd/releases/download/v$VERSION
sudo dnf install $REL/postmarkd_${VERSION}_linux_$ARCH.rpm
This installs the binary, systemd units, a postmarkd user and
/etc/postmarkd/postmarkd.conf. Then add your token, set a sender and start it:
sudo install -m 0600 /dev/stdin /etc/postmarkd/server-token <<< 'your-postmark-server-token'
sudoedit /etc/postmarkd/postmarkd.conf # set [email protected]
sudo systemctl enable --now postmarkd
sudo usermod -aG postmarkd "$USER" # let your user use the socket (log in again)
echo "Hello from $(hostname)" | postmarkd send -to [email protected] -subject "It works"
Make every program on the host send through Postmark:
sudo ln -s /usr/bin/postmarkd /usr/sbin/sendmail
echo "Subject: test" | sendmail [email protected]
Binaries, packages and checksums for every version are on the
releases page. To build
from source instead: git clone https://github.com/rossedman/postmarkd && cd postmarkd && sudo make install.
More options in Getting started.
docker run -d --name postmarkd -p 127.0.0.1:8025:8025 -p 127.0.0.1:2525:2525 \
-e POSTMARKD_SERVER_TOKEN=your-postmark-server-token \
-e [email protected] \
ghcr.io/rossedman/postmarkd:0.1
curl localhost:8025/email -d '{"To":"[email protected]","Subject":"Hi","TextBody":"Hello"}'
As a sidecar, see Containers and the examples: Docker Compose, Kubernetes native sidecar, GitHub Actions service.
docker run -d -p 8025:8025 -p 2525:2525 -e POSTMARKD_BLACKHOLE=true ghcr.io/rossedman/postmarkd:0.1
# … run your test suite against localhost:8025 or SMTP localhost:2525 …
curl -s localhost:8025/blackhole/messages | jq '.Messages[].Message.Subject'
postmarkd is rarely the bottleneck: a send to Postmark takes 100 ms or more, while postmarkd's own work takes microseconds. Benchmarks run on every CI build, and pull requests fail if they add allocations or make a benchmark more than 25% slower.
Measured on a 4 vCPU Intel Xeon @ 2.80GHz with Go 1.24, upstream replaced by an instant fake (medians of 6 runs):
| Benchmark | Result | Before optimizing |
|---|---|---|
| End to end over the unix socket (client + daemon on the same 4 CPUs) | 33,700 messages/s, 30 µs each | 21,600/s |
| Full relay: client → postmarkd → pooled HTTP → fake Postmark | 15,400 messages/s | 13,100/s |
| Batch of 500 through the HTTP handler | ~115,000 messages/s, 4.3 ms per batch | 7.0 ms |
| Validate and apply defaults to one message | 4.4 µs, 6 allocations | 17.8 µs, 66 |
| Same for a 500-message batch | 4.0 ms, 172 KB | 9.7 ms, 2 MB |
| SMTP message → Postmark (MIME parse + send) | 22 µs | 36 µs |
| MIME conversion, 100 KB PDF attachment | 0.7 ms, 190 MB/s | 1.25 ms |
| Recording metrics for a request (lock-free) | 215 ns, 0 allocations | 293 ns |
| Prometheus scrape | 13 µs, 5 allocations | 67 µs, 386 |
In practice, throughput is set by POSTMARKD_MAX_CONCURRENCY ÷ Postmark's
latency (128 ÷ 0.25 s ≈ 500 requests/s, or up to 256,000 messages/s with
500-message batches) and by your Postmark account's limits. Details, what each
benchmark measures and how to run them: docs/benchmarks.md.
Postmark signs your mail with DKIM once you add DNS records for your domain; a custom Return-Path makes SPF line up with your domain; and a DMARC record tells receivers what to do with mail that fails both. Gmail and Yahoo require these for bulk senders. Check them in one command:
postmarkd check-domain yourdomain.com -account-token "$POSTMARK_ACCOUNT_TOKEN"
It prints each record's status and the exact record to add when one is missing. Read Email authentication for what these mean.
The full documentation is at rossedman.io/postmarkd,
built from the docs/ folder:
Contributions are welcome. See CONTRIBUTING.md and the Code of Conduct. To report a security issue, see SECURITY.md.
MIT. postmarkd is not affiliated with or endorsed by Postmark or ActiveCampaign.