Quickstart · Dashboard · Metrics · Alerting · Deployment
Your transactional email fails silently: the appointment confirmation bounces, the password reset never arrives, and you find out from a support ticket three days later. Resend knows the moment it happens — resend-exporter turns that moment into a Prometheus alert.
It receives Resend webhook events, verifies their Svix signatures, and exposes clean Prometheus metrics — so the monitoring stack you already run pages you when email breaks.
- Alert on what matters — a bounced confirmation email is an incident, not a dashboard curiosity.
increase(resend_email_events_total{event_type="email.failed"}[5m]) > 0and you're paged. - Metrics stay clean, logs stay detailed — counters answer "how many & how bad", structured JSON logs answer "which email & why". Recipient addresses and subjects are redacted or hashed by default, so metrics and dashboards are safe to share.
- Cardinality-safe by construction — recipient domains are bucketed (well-known providers keep their name, the long tail becomes
other), so one busy tenant can't blow up your Prometheus. - Boring to operate — a single compiled binary in a distroless image, three runtime dependencies, no database, no Resend API key needed.
flowchart LR
R[Resend] -->|"Svix-signed webhooks"| E["resend-exporter<br/>verify · count · log"]
E -->|"/metrics"| P[Prometheus]
P --> G["Grafana<br/>dashboards + alerts"]
E -.->|"JSON logs"| L[(Loki / stdout)]
| Method | Path | Purpose |
|---|---|---|
POST |
/webhooks/resend |
Receive Resend webhook events |
GET |
/metrics |
Prometheus metrics |
GET |
/healthz |
Liveness probe |
GET |
/readyz |
Readiness probe |
Docker
docker run -p 8080:8080 \
-e RESEND_WEBHOOK_SECRET=whsec_... \
ghcr.io/robbeverhelst/resend-exporter:latestHelm
helm install resend-exporter oci://ghcr.io/robbeverhelst/charts/resend-exporter \
--set resend.webhookSecret=whsec_... \
--set serviceMonitor.enabled=trueLocal playground — exporter + Prometheus (alert rules loaded) + Grafana (dashboard provisioned):
docker compose up --buildThen point a Resend webhook at https://your-host/webhooks/resend (dashboard → Webhooks → Add, copy the whsec_ signing secret) and scrape /metrics. The webhook path needs to be internet-reachable; /metrics should not be. Details in docs/deployment.md.
Ships in examples/grafana/dashboards/ — import it, or let the compose playground / your dashboard sidecar provision it:
resend_webhook_events_total{event_type="email.bounced",domain="acme.dev"}
resend_email_events_total{event_type="email.bounced",from_domain="acme.dev",to_domain="outlook.com"}
resend_webhook_signature_failures_total
resend_webhook_handler_errors_total{reason="invalid_json"}
resend_webhook_last_event_timestamp_seconds{event_type="email.delivered"}
Full reference and the cardinality design: docs/metrics.md. Ready-made alert rules (any failure, bounces, repeated delays, signature failures, stale webhook): docs/alerting.md and examples/prometheus/alerts.yml.
| Variable | Required | Default | Description |
|---|---|---|---|
RESEND_WEBHOOK_SECRET |
yes | — | Svix signing secret (whsec_...) from the Resend webhook settings |
RESEND_EXPORTER_ADDR |
no | :8080 |
Listen address (host:port or :port) |
RESEND_EXPORTER_WEBHOOK_PATH |
no | /webhooks/resend |
Webhook path |
RESEND_EXPORTER_METRICS_PATH |
no | /metrics |
Metrics path |
RESEND_EXPORTER_LOG_LEVEL |
no | info |
debug, info, warn, error |
RESEND_EXPORTER_REDACTION_MODE |
no | strict |
strict, hash, or none — see docs/configuration.md |
RESEND_EXPORTER_TO_DOMAIN_ALLOWLIST |
no | — | Extra recipient domains kept as their own to_domain label value |
RESEND_EXPORTER_SERIES_HOLD_SECONDS |
no | 60 |
New-series hold window; set >= your longest scrape interval |
- Configuration — every environment variable, redaction modes
- Metrics — metric reference and cardinality design
- Alerting — PromQL alert examples for Grafana/Alertmanager
- Deployment — Docker, Helm, docker-compose, exposing the webhook
- Development — building, testing, and the release process
resend-exporter follows semver. As of 1.0.0, the following are stable interfaces — breaking changes to any of them mean a major release:
- Configuration environment variables and their semantics
- Metric names, types, and label sets
- HTTP endpoints and their behavior (paths themselves are configurable)
- Helm chart values
- The structured log schema (field names; redaction guarantees)
- Optional Resend API reconciliation: backfill missed events after downtime, verify webhook configuration, and enrich sparse events (
RESEND_API_KEYis already reserved for this) - Delivery-delay histogram and bounce-type breakdown metrics
Non-goals: replacing the Resend dashboard, storing email content, and sending alert notifications directly — routing belongs in Grafana Alerting, Alertmanager, ntfy, Slack, or PagerDuty.
PRs welcome — see CONTRIBUTING.md. The short version: bun install, bun test, conventional commits (every feat:/fix: on main ships a release automatically).

