WarmHawk
Docs / Reference / Guardrails & compliance

Enforced structurally, not just documented.

Every guardrail on this page lives in the one shared send/ingest path — not duplicated (and possibly forgotten) per API entry point, and not something a caller can route around.

WarmHawk enforces CAN-SPAM (a mailing address per sending domain + unsubscribe), RFC 8058 one-click unsubscribe, an EU AI Act disclosure marker, GDPR erasure, CSV-injection defense, a bounce/complaint circuit breaker (auto-pause past 5% with a 20-send minimum sample), login brute-force throttling, continuous blocklist monitoring, and AES-256-GCM credential encryption — all structurally, in the API itself, not as optional dashboard settings.

CAN-SPAM auto-injection

A send is refused before it reaches the pipeline if the sending domain has no physical mailing address, or there is no unsubscribe link to put in the footer — both are unconditional, structural checks in the one shared send path, not per-entry-point duplicated logic a caller could route around.

A mailing address per domain

The footer address belongs to the domain a mailbox sends from, so an agency’s client brands each print their own address and never share one. There is no install-wide fallback. Adding a domain never needs an address; launching a campaign that sends from it does, and the launch check names every domain still missing one.

Built-in unsubscribe page

A campaign with no unsubscribeUrlTemplate of its own links every email to a page WarmHawk serves on your install’s domain, at /unsubscribe/ plus a signed token that names the lead without putting the address in the URL. Opening the page changes nothing, because mail scanners follow links; pressing the button suppresses the address in every campaign. A suppressed address is also refused at send time, so a lead who unsubscribes with an email already queued does not get it.

RFC 8058 one-click unsubscribe

List-Unsubscribe and List-Unsubscribe-Post: List-Unsubscribe=One-Click headers are generated server-side on every send, letting Gmail and Yahoo unsubscribe a recipient with a single POST, no login required — never left to template configuration.

EU AI Act Article 50 disclosure

A disclosure marker is auto-appended whenever a send is genuinely AI-generated AND the recipient resolves to an EU-region signal. A fallback send (AI unavailable, using the plain template) has nothing to disclose, so the marker only appears on real AI-written content.

GDPR erasure

DELETE /v1/leads/erase anonymizes a data subject’s PII across every campaign by email, preserving aggregate counts; DELETE /v1/leads/:id is a real hard delete of one row. Both are structural rights, not admin-only tooling.

CSV-injection defense

A cell value starting with =, +, -, or @ is neutralized on ingest (both CSV import and webhook), before it is ever stored — so a malicious lead field can’t turn into a spreadsheet formula when you later export and open the data in Excel or Sheets.

Bounce/complaint circuit breaker

A campaign/mailbox pair auto-pauses (pausedForBounceRate: true) once its rolling bounce rate exceeds 5%, but only after at least 20 sends — enough to be a real signal, not one unlucky bounce. Catches a bad list before it damages a domain’s reputation, not after.

Login brute-force throttle

POST /v1/auth/login is both rate-limited (10 requests/minute) and per-account locked out after repeated failures — two independent layers, since rate limiting alone doesn’t stop a slow, patient attacker targeting one account.

Continuous blocklist/DNSBL monitoring

Every domain check runs against Spamhaus ZEN/DBL, Barracuda, and SORBS in addition to SPF/DKIM/DMARC — a one-time check at setup doesn’t catch a domain landing on a blocklist days later.

Credential encryption at rest

Mailbox SMTP/IMAP passwords, OAuth refresh tokens, and BYOK AI provider keys are all AES-256-GCM encrypted server-side before persisting, and never echoed back in any API response — not even to the authenticated caller who just set them.

Rate limits

RouteLimit
POST /v1/auth/login10 requests / minute
POST /v1/leads/import10 requests / minute
POST /v1/leads/webhook30 requests / minute
Public domain-check tool20 requests / minute
Every other route100 requests / minute (global default)

The send-pipeline guardrails on this page are all inside warmhawk-core-engine. For the licensing/billing security model — RSA-signed licenses, Stripe webhook verification — see Stripe checkout & webhooks. For the per-guardrail request/response detail, see Leads & enrichment and Sending safely & domain health.

Questions

Guardrails & compliance: questions worth answering up front

Are these guardrails enforced by the dashboard, or by the API itself?+

The API itself, in the one shared send path — the dashboard (Tier 1/2) is a UI on top of the same engine, not a separate enforcement layer. A direct API call gets exactly the same guardrails as a dashboard-triggered send.

Can I raise the bounce circuit breaker’s 5% threshold?+

Yes — it’s configurable per campaign via bounceRateThreshold on PATCH /v1/campaigns/:id. The 20-send minimum sample size before it can trip at all is not currently configurable.

What happens to a lead’s data if I never call the erasure endpoint?+

It stays as-is indefinitely — WarmHawk doesn’t auto-expire lead data on any schedule. Erasure (DELETE /v1/leads/erase) is something you call when a data subject makes a request, not a background job.

Where does license/billing security fit into this list?+

Separately — RSA-signed license issuance and Stripe webhook verification are covered in the checkout/billing docs, not here. This page is specifically about sending-pipeline and data-handling guardrails inside warmhawk-core-engine.