Schedule icon
Request icon
Return icon
Switch icon
Log icon
SlackIncomingWebhook icon

Postmark Bounce and Spam Rate Sentinel

Read Postmark outbound stats every four hours, send nothing, and alert Slack when bounce or spam complaint rates cross your warning and critical lines.

Categories
Business

Transactional email breaks quietly. Someone imports an old customer list, a signup form starts accepting typos, or a marketing send goes out on the transactional stream, and the bounce rate jumps. A few days later password resets and receipts land in spam or stop being delivered, because the provider protects its shared reputation by reviewing or pausing accounts with high bounce or complaint rates. This blueprint turns a scheduled, read-only call to the Postmark Stats API into a sending reputation gate: io.kestra.plugin.core.http.Request reads the outbound overview, one jq query checks the bounce and spam complaint rates against your limits, and the result routes to a quiet log or a Slack alert.

How it works

  1. get_outbound_stats (io.kestra.plugin.core.http.Request) calls GET /stats/outbound with the server token in X-Postmark-Server-Token, for one messagestream and a fromdate/todate window in US Eastern time (Postmark's stats timezone). It is a read: no email is sent and no bounce is reactivated. Retries cover short network blips, and allowFailed plus allowFailure keep an API error from aborting the flow so it can be reported instead.
  2. summarize_reputation (io.kestra.plugin.core.debug.Return) first guards for a missing or non-200 response (UNREACHABLE). Otherwise one jq query reads Sent, Bounced, SMTPApiErrors, BounceRate, SpamComplaints and SpamComplaintsRate. Fewer than min_sent sent is LOW_VOLUME; a bounce or spam complaint rate at or above its critical line is CRITICAL; at or above its warning line is WARNING; otherwise HEALTHY. The summary names which rate tripped.
  3. route_by_reputation (io.kestra.plugin.core.flow.Switch) branches on that classification: HEALTHY and LOW_VOLUME log quietly, WARNING posts a Slack note, CRITICAL and UNREACHABLE page Slack. A defaults fallback logs anything unexpected.
  4. log_audit_result (io.kestra.plugin.core.log.Log) writes one audit line per run with the HTTP status and the full summary.
  5. The flow-level errors block pages Slack if the flow itself fails, and the every_four_hours trigger runs at 40 minutes past every fourth hour, shipped disabled: true.

What you get

  • A read-only reputation check every four hours with no custom scripting and no extra plugin to install.
  • A status output (HEALTHY / WARNING / CRITICAL / LOW_VOLUME / UNREACHABLE) other flows can gate on, for example to hold a bulk send or a list import while rates are high.
  • Slack messages that say which rate tripped, with the bounced, complaint and sent counts.
  • An audit line on every run, separate from Slack.

Who it's for

  • SaaS and e-commerce teams that send receipts, password resets and notifications through Postmark.
  • Growth teams that also send broadcasts on a separate Postmark stream and want each stream judged on its own.
  • Teams that already run their notification and alerting workflows in Kestra.

Why orchestrate this with Kestra

Postmark shows bounce and spam rates on its dashboard and can send a webhook per bounce, but nothing tells your team that the rate crossed a line today. Kestra turns a plain, read-only API call into a scheduled, typed classification with retries, a failure path and an execution history, and the status output lets your sending flows pause themselves before the provider does it for you.

Prerequisites

  • A Postmark server and its Server API token (the server's API Tokens tab).
  • The ID of the message stream to judge, shown on the stream's settings page (outbound is the default transactional stream).
  • A Slack incoming webhook for the WARNING, CRITICAL, UNREACHABLE and flow-failure alerts.

Secrets

  • POSTMARK_SERVER_TOKEN: the Server API token of the Postmark server to watch.
  • SLACK_WEBHOOK: Slack incoming webhook used for every alert branch and the flow-failure alert.

In Kestra OSS, secrets are supplied as environment variables prefixed SECRET_ and base64-encoded, for example SECRET_POSTMARK_SERVER_TOKEN=$(echo -n 'your-server-token' | base64), and read back with {{ secret('POSTMARK_SERVER_TOKEN') }}. Use the Enterprise secrets backend for encryption at rest and access control.

Quick start

  1. Add POSTMARK_SERVER_TOKEN and SLACK_WEBHOOK as secrets.
  2. Run the flow manually with a low min_sent and compare the counts and rates in the logs with the stream's statistics page in Postmark.
  3. Lower warning_bounce_rate below your current rate to see the WARNING path, then set it back.
  4. Enable every_four_hours.

How to extend

  • Run one execution per message stream (transactional and broadcast) with its own limits.
  • Add GET /bounces?type=HardBounce&count=50&offset=0 to list the addresses behind a spike in the same alert.
  • Pass a tag query parameter to judge one email type, such as receipts or password resets.
  • Store the last status in a KV store and alert only when it changes.

Inputs

  • lookback_days: type INT, default 1.
  • message_stream: type STRING, default outbound.
  • warning_bounce_rate: type FLOAT, default 5.0 (percent).
  • critical_bounce_rate: type FLOAT, default 10.0 (percent).
  • warning_spam_rate: type FLOAT, default 0.05 (percent).
  • critical_spam_rate: type FLOAT, default 0.1 (percent).
  • min_sent: type INT, default 200.

Outputs

  • status: one of HEALTHY, WARNING, CRITICAL, LOW_VOLUME, UNREACHABLE.
  • Example path: {{ fromJson(outputs.summarize_reputation.value).reason }} for the rate that tripped.

Links

Pitfalls

  • Postmark's stats are per day in US Eastern time, so lookback_days: 1 covers yesterday and today, not a rolling 24 hours.
  • The Stats API takes a server token, not an account token; with the wrong token the run reports UNREACHABLE.
  • The defaults are starting points, not Postmark policy; tune them to your own sending history.
  • This flow only reads; it never sends an email or reactivates a bounced address.
See How

New to Kestra?

Use blueprints to kickstart your first workflows.