me.squibble.email — Feature Overview

me.squibble.email is a secure, rate-limited, token-based email gateway for AI agents and applications. It provides inbound IMAP inbox access and a full outbound email pipeline — from API submission through SMTP delivery, open/click tracking, and automated bounce handling.


Feature Matrix

Outbound sending

CapabilityDetail
Send endpointPOST /api/v1/messages/send — returns 202 Accepted with per-recipient UUIDs
Per-recipient rowsOne outbound_messages row per recipient; enables individual tracking, bounce attribution, and suppression
HTML auto-enhancementTracking pixel injected, all <a href> links rewritten to the tracking gateway
Sender bindingFrom, Sender, Reply-To, Return-Path are always the authenticated mailbox’s address — no caller override. The From display name is operator-configurable via mailboxes:update --display-name.
Input caps≤100 combined recipients, ≤1 MiB body, ≤998-octet subject
IdempotencyIdempotency-Key header with ≥24 h dedup window prevents duplicate sends on network retries
Per-token quotassend_limits (hourly/daily) and allowed_recipient_domains on each JWT token

Delivery queue

CapabilityDetail
PostgreSQL queueSELECT FOR UPDATE SKIP LOCKED — no external broker needed
At-least-once deliveryclaim → deliver → finalize pattern; reaper loop recovers crashed workers
TLS enforcementPer-mailbox smtp_tls_mode: implicit (port 465) or starttls_required (port 587, fail-closed)
Retry policyExponential back-off up to 10 attempts; SMTP 5xx fast-fails immediately
PII scrubbingRecipient addresses in SMTP error responses are redacted before error_log storage
One-click unsubscribeList-Unsubscribe + List-Unsubscribe-Post headers injected automatically (RFC 8058 / Gmail/Yahoo 2024 requirements)

Open & click tracking

CapabilityDetail
Open trackingGET /track/open/{signed_token}.gif — 1×1 GIF, updates opened_at once
Click trackingGET /track/click/{signed_token}?url=…&sig=… — 302 redirect, updates clicked_at once
HMAC-signed tokensRaw UUIDs are never in tracking URLs; invalid signatures rejected without DB access
Rate limiting120 req/min per IP on tracking endpoints

Bounce processing

CapabilityDetail
VERP authenticationReturn-Path is bounce+{msg_id}.{hmac16}@domain; forged DSNs are quarantined
RFC 3463 classificationtransient (4.x.x) / permanent (5.x.x) / unknown
Suppression listPermanent bounces auto-insert to suppressions; future sends to that address get 422
Suppression enforcementSingle batch query — no per-recipient round-trip at send time
Separate processBounce cron runs as a one-shot sync CLI (bounces:poll-once), scheduled externally

Security & compliance

CapabilityDetail
Credential encryptionSMTP and IMAP passwords encrypted with MultiFernet (supports key rotation)
JWT scopesmessages:send scope required; tokens carry per-token recipient domain allowlists and send-rate caps
DKIM / SPF / DMARCHandled at the MTA layer (see docs/runbooks/deliverability.md)
No open relaySender identity bound to the issuing mailbox at all times

Observability

MetricDescription
outbound_messages_enqueued_totalMessages accepted by the send endpoint
outbound_messages_sent_totalSuccessful SMTP deliveries
outbound_messages_failed_total{reason}Permanent failures by error class
outbound_messages_bounced_total{type}Bounces by classification
smtp_delivery_duration_secondsSMTP round-trip histogram
queue_depth / queue_oldest_age_secondsQueue health (sync collector at scrape time)
tracking_opens_total / tracking_clicks_totalEngagement events
bounce_cron_*_totalBounce cron polled / rejected / suppressions inserted

Admin CLI

mailboxes:create / :update / :destroy / :validate
tokens:issue / :revoke / :list
messages:list / :retry / :cancel
suppressions:list / :remove
bounces:poll-once
db:init / :migrate / :current / :history / :backup

Architecture at a glance

Agent / App


POST /api/v1/messages/send  ──►  outbound_messages (status=queued)

                                  async worker polls

                                  SMTP delivery

                              ┌─────────┴──────────┐
                           success               failure
                              │                     │
                        status=sent          5xx → status=failed
                                             4xx → retry (backoff)

Recipient opens email

GET /track/open/{token}.gif  →  opened_at set

Recipient clicks link

GET /track/click/{token}?url=…&sig=…  →  clicked_at set  →  302 to destination

MTA returns DSN

bounces:poll-once (sync CLI, every 5 min)

    ├── HMAC valid + permanent  →  status=bounced, suppressions INSERT
    ├── HMAC valid + transient  →  status=bounced (no suppression)
    └── HMAC invalid            →  Rejected-Bounces IMAP folder

Reference docs

DocPurpose
README.mdGetting started, local dev, CLI reference
CONTRIBUTING.mdCoding standards and invariants (formerly AGENTS.md)
docs/architecture.mdTechnical deep-dive
docs/adr/Architecture decisions (scope change, bounce topology, fan-out model)
docs/runbooks/deliverability.mdDKIM/SPF/DMARC setup, bulk-sender compliance
docs/runbooks/backup-posture.mdBackup strategy, restore procedures
docs/runbooks/key-rotation.mdFernet key rotation — three-phase procedure with re-encryption script
docs/features/01_database_and_config.mdPhase 1: schema & CLI
docs/features/02_outbound_api_and_html_parsing.mdPhase 2: send endpoint & HTML parsing
docs/features/03_async_worker_queue.mdPhase 3: delivery worker
docs/features/04_tracking_endpoints.mdPhase 4: open & click tracking
docs/features/05_imap_bounce_processing.mdPhase 5: bounce processing & suppressions

Sourced from docs/features/00_overview in the repo. Edits go through the same review as code.