Skip to content
Pidgely Docs

Platform

Architecture

How Pidgely accepts, paces, signs, routes and tracks email - one Go binary in stateless roles over PostgreSQL, with its own MTA and feedback loop.

View as Markdown

Pidgely is one Go binary and a PostgreSQL database. Knowing how it works helps you reason about timing, retries and what each webhook means.

Text
                 HTTPS (REST API, SDKs)                              SMTP :25 (recipient MX)
 your app ───────────────► api ──┐                                   ┌────────► MTA (IP pools, warm-up)
 your app ── SMTP 587/465 ► smtp ─┤        PostgreSQL                 │              │ blocked / unreachable
                                  ├──► messages, deliveries, events   │              ▼
                                  │    contacts, broadcasts, jobs ◄───┴─ worker ─► second own node (fallback)
 mailbox providers ─ DSN/ARF ─► inbound                                     └──► signed webhooks ──► your app

Roles

The binary runs in roles: api (HTTP API), smtp (submission), worker (delivery, webhooks, broadcast fan-out), inbound (bounces and complaints) or all on one node. Every role is stateless and keeps its state in PostgreSQL, so each one scales horizontally on its own (Scaling).

Life of an email

  1. Accept. The API and SMTP share one pipeline. It validates the message (verified From domain, allowed for the key; recipient count; headers; attachments; template), then in one transaction reserves the quota, drops suppressed recipients, writes the message with one delivery per recipient, and enqueues one delivery job per recipient. You get the id only after that commit, so an accepted email is never lost.
  2. Pace. Each account books delivery slots at its own rate, so a burst from one customer never delays the others.
  3. Deliver. Before each attempt the worker re-checks cancellation, the schedule, account status, suppression and domain verification. It adds per-recipient headers (Feedback-ID, and List-Unsubscribe on the broadcast stream), signs DKIM and sends with a VERP return path.
  4. Route. Pidgely's own MTA is tried first, with per-stream IP pools, warm-up caps and per-destination concurrency. A blocked or unreachable result can fall back to a second own node; a permanent result becomes a bounce and a suppression; a transient result is retried with backoff for up to 72 hours.
  5. Feedback. Asynchronous bounces (DSN) arrive on the VERP address, complaints arrive through feedback loops (ARF), unsubscribes through the one-click link. Each updates the delivery, the suppression list and usage, and emits events and webhooks.

Jobs are delivered at least once and every handler is idempotent, which is why webhooks carry a stable id for de-duplication.

Deliverability by design

  • DKIM RSA-2048 per domain (relaxed/relaxed, From/Subject/To oversigned, List-Unsubscribe signed).
  • Return path on a subdomain of your domain (MX to Pidgely, SPF include), so SPF and DKIM both align for DMARC.
  • A text part generated from HTML; Message-ID on your domain; CRLF line endings.
  • EHLO with each IP's PTR name; opportunistic TLS by default, tls: enforced per domain.
  • Separate IP pools for transactional and broadcast mail; gradual warm-up of new IPs.

Security

  • API keys and session tokens are stored only as HMAC/SHA-256 digests. DKIM private keys and webhook secrets are encrypted at rest (AES-256-GCM).
  • Unsubscribe, bounce (VERP) and attachment-download tokens are HMAC-signed.
  • Outbound requests (webhooks, remote attachments) pass an SSRF guard: the IP is checked after DNS resolution, private ranges are blocked and redirects are not followed.
  • Reserved headers and CR/LF injection are rejected, executable attachments are blocked, SMTP submissions are sanitized.
  • New accounts start in a sandbox with plan quotas, and the reputation guard pauses accounts with abnormal bounce or complaint rates.

See Security for how to report a vulnerability.

Self-hosting

Pidgely runs as a single binary with PostgreSQL 18, from a single small server up to separate fleets per role. Self-hosted deployments are not generally available during early access; contact us if you need one.

Multi-channel (roadmap)

The core is channel-agnostic: messages have a channel, there is one delivery per recipient, and events, suppression, quotas and webhooks are generic. SMS, WhatsApp and push are planned on the same model. They are not available yet.