From f64c7e892290db81e2fffdabf48486754fd8f951 Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Wed, 27 May 2026 04:51:27 +0000 Subject: [PATCH] site: full redesign of company, legal, and deliverability pages Ten pages rewritten end to end against the shared HeroAtmosphere hero, container-page max-width, and sky color tokens, so they finally match the visual language of the product pages (sending, warmup, developers, roadmap, changelog). Company: - about: open-source positioning, control plane vs execution plane origin essay, six numbered build principles, posture comparison, real repo path strip, small-team note linking to roadmap and changelog and GitHub. - brand: marks grid with wordmark and mark variants plus clear-space and min-size strip, full sky palette ramp with hex and token names, typography specimen (Inter plus system mono), four voice principles, do and dont misuse grid, press contact and download manifest. - trust: posture badges with shipped vs roadmap items called out honestly, real envelope encryption flow (KMS, per-user DEK, AES-GCM, Redis-cached plaintext DEK, encrypted DEK in DynamoDB) with source anchors, worker boundary table, collect vs never-collect split, real auto-block thresholds, subprocessors link, responsible disclosure with security@warmbly.com only, status and changelog quick links. Deliverability: - mailbox-first positioning, per-mailbox health dashboard mocking the five real states (healthy, watch, throttled, quarantined, blocked), opinionated four-point stance, defaults spec sheet pulled straight from CLAUDE.md (50/day, 600s, 10/day, 40/day, +1/day, 100/5min, 500/hr, 3/24h tokens, score > 50), four-pool isolation card grid, auto-quarantine band gauge with thresholds and Google/SES anchors, SPF/DKIM/DMARC/PTR coverage grid, six-row suppression engine table, recommended cold posture, by-the-numbers strip with source paths, FAQ and CTA. Legal (consistent treatment, sticky sidebar TOC + numbered sections): - terms: 17 numbered sections, Stripe payment processor note, placeholder governing-law phrasing, legal@warmbly.com contact. - privacy: 13 sections mirroring the real encryption model in copy, print stylesheet for clean printing. - dpa: 20 sections plus three annexes, SCC and UK IDTA references, 72-hour breach notification, sub-processor authorization model, cross-references to terms and trust and subprocessors. - acceptable-use: opinionated 14-section AUP, allowed vs not-allowed two-column grid, real mailbox health enforcement ladder, no fake enforcement statistics, abuse@warmbly.com. - subprocessors: designer-grade table of real vendors only (AWS, Cloudflare, Stripe, Postmark, Sentry, Plain) with real security or privacy URLs, mobile stacked-card fallback, subscribe-to-changes mailto, evaluation criteria, notice and objection window. - cookies: per-category tables (strictly necessary, functional, third-party) with real cookie names, analytics and marketing explicitly empty rather than invented, manage-preferences anchor. Constraints enforced across all ten: no em dashes, no fabricated metrics, no fake compliance badges, no fictional company or employee names, no AI-cute filenames or terminal logs. Astro JSX parser caveat respected throughout (no < or <= comparisons inside JSX expression blocks; all comparisons hoisted to frontmatter). --- site/src/pages/about.astro | 492 ++++++++++++--- site/src/pages/acceptable-use.astro | 609 +++++++++++++++++-- site/src/pages/brand.astro | 765 ++++++++++++++++++++--- site/src/pages/cookies.astro | 704 +++++++++++++++++++-- site/src/pages/deliverability.astro | 909 +++++++++++++++++++++++++--- site/src/pages/dpa.astro | 645 +++++++++++++++++--- site/src/pages/privacy.astro | 546 ++++++++++++++--- site/src/pages/subprocessors.astro | 509 ++++++++++++++-- site/src/pages/terms.astro | 490 ++++++++++++--- site/src/pages/trust.astro | 623 +++++++++++++++---- 10 files changed, 5536 insertions(+), 756 deletions(-) diff --git a/site/src/pages/about.astro b/site/src/pages/about.astro index c65e3156..1965d1f8 100644 --- a/site/src/pages/about.astro +++ b/site/src/pages/about.astro @@ -1,94 +1,393 @@ --- import Layout from '../layouts/Layout.astro'; -import Cloud from '../components/Cloud.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; import Icon from '../components/Icon.astro'; import CTA from '../components/CTA.astro'; -const beliefs = [ - ['Cold mail is a privilege', 'We treat shared reputation infrastructure as exactly that. One bad sender taxes everyone.'], - ['Mailbox-first, not worker-first', 'Caps belong on mailboxes. Workers respect the sum of their mailbox budgets. Always.'], - ['Safe defaults', 'Defaults should keep you safe even if you never change them. We tell you what we think first.'], - ['No black boxes', 'Every score, suppression and quarantine has a reason you can read.'], - ['Customer-readable metrics', 'You should be able to read the same numbers we would ask about in an incident.'], - ['Open source by default', 'Apache 2.0 on GitHub. Self-host the whole platform if you want to.'], +// All facts on this page are sourced from the Warmbly repo: +// internal/app/worker/assignment.go, internal/app/cipher/*, +// internal/infrastructure/kms/*, internal/repository/pg_warmup.go, +// internal/scheduler/*, internal/config/constants.go, README.md. +// No fabricated metrics, no fictional employees, no invented customers. + +const facts = [ + { k: 'License', v: 'Apache 2.0', why: 'Permissive. Fork it, audit it, run it inside your own VPC.' }, + { k: 'Self-hostable', v: 'Yes', why: 'Backend, consumer, workers, tracking, realtime, web. All buildable from the repo.' }, + { k: 'Roadmap', v: 'Public', why: 'Every shipped, in-progress, and researched item lives on /roadmap/.' }, + { k: 'Encryption model', v: 'KMS envelope', why: 'AES-256-GCM. Per-user DEK. Encrypted blob in DynamoDB, plaintext only in cache.' }, + { k: 'Worker SQL access',v: 'None', why: 'Workers boot Kafka, Redis, KMS, DynamoDB, S3. They never open a Postgres connection.' }, + { k: 'Default cold cap', v: '50 / mailbox', why: 'Per-mailbox, not per-worker. A worker is the sum of its mailbox budgets.' }, +]; + +const planes = [ + { + tag: 'Control plane', + color: '#7dd3fc', + accent: '#0284c7', + title: 'Backend + consumer', + body: 'Owns the relational state. Decides which mailbox sends what, when, and on which worker. Reads Postgres. Speaks to Stripe, KMS, S3. Never opens an SMTP socket itself.', + parts: ['cmd/backend', 'cmd/consumer', 'Postgres', 'Kafka producer'], + }, + { + tag: 'Execution plane', + color: '#10b981', + accent: '#059669', + title: 'Workers', + body: 'One process per machine. One IP per worker. Receives commands from a worker-specific Kafka topic. Sends, syncs, validates, heartbeats. State is disposable. A crashed worker is replaced by another reading the same topic.', + parts: ['cmd/worker', 'Kafka consumer', 'Redis cache', 'KMS + DynamoDB + S3'], + }, +]; + +const principles = [ + { + n: '01', + title: 'Open source by default.', + body: 'The whole platform is on GitHub under Apache 2.0. Sending logic, warmup pools, abuse thresholds, fraud heuristics. If you want to audit how a quarantine decision was made, you can read the code that made it.', + }, + { + n: '02', + title: 'Mailbox-first safety, not worker-first.', + body: 'A worker is not a flat send limit. Its safe daily volume is the sum of its mailbox budgets, full stop. We will not ship a feature that bypasses the per-mailbox cap to make a chart look better.', + }, + { + n: '03', + title: 'Many workers, many IPs.', + body: 'Sending is intentionally distributed. No central choke-point that becomes a single reputation liability. Adding volume means adding mailboxes and workers, not cranking a few harder.', + }, + { + n: '04', + title: 'Quarantine before providers do.', + body: 'Our thresholds for the shared paid warmup pool are stricter than what Google, Microsoft, or SES will tolerate. We act in the warning band, not the catastrophe band. By the time a provider penalises you, we have already pulled the mailbox.', + }, + { + n: '05', + title: 'Encrypted by design.', + body: 'Application secrets and sensitive payloads use AWS KMS envelope encryption. A per-user DEK. AES-256-GCM at the field level. Plaintext keys only ever live in a Redis cache with a TTL. The repo says it. The migrations enforce it.', + }, + { + n: '06', + title: 'No black-box scoring.', + body: 'Every health band, every suppression entry, every pool block has a reason you can read in the dashboard and a row you can read in the database. The product owes you the same explanation in both places.', + }, +]; + +// Differentiation. Positioning only. No fabricated competitor claims. +const positioning = [ + { + label: 'Source code', + warmbly: 'Apache 2.0 on GitHub. Fork, audit, run.', + closed: 'Closed source. You see the dashboard, not the engine.', + }, + { + label: 'Self-hosting', + warmbly: 'Self-host the whole platform if you want to.', + closed: 'SaaS only. Your data lives where the vendor decides.', + }, + { + label: 'Sending architecture', + warmbly: 'Distributed workers, one IP each, per-mailbox caps.', + closed: 'Typically opaque. You buy throughput, not architecture.', + }, + { + label: 'Abuse and quarantine logic', + warmbly: 'Thresholds documented in this repo. Stricter than provider enforcement.', + closed: 'Undocumented. You find out when your reputation is gone.', + }, + { + label: 'Roadmap', + warmbly: 'Public board. Shipped, in progress, researching.', + closed: 'Internal. Released on announcement cadence.', + }, + { + label: 'Pricing posture', + warmbly: 'Per-workspace, not per-inbox. Free tier and a public free repo.', + closed: 'Per-inbox license fees that scale with team size.', + }, +]; + +// Project shape, sourced from the repo layout in README.md. +const surface = [ + { path: 'cmd/backend', desc: 'REST API and business orchestration. Owns Postgres.' }, + { path: 'cmd/consumer', desc: 'Kafka consumer that folds worker results into platform state.' }, + { path: 'cmd/worker', desc: 'Distributed execution worker. One IP per machine. No SQL.' }, + { path: 'tracking/', desc: 'Open and click tracking edge service.' }, + { path: 'realtime/', desc: 'WebSocket fanout for the dashboard, written in Elixir.' }, + { path: 'web/', desc: 'Frontend dashboard for sending, warmup, inbox, and CRM.' }, + { path: 'internal/app/cipher', desc: 'Envelope encryption primitives for application secrets.' }, + { path: 'internal/app/worker', desc: 'Worker assignment, tiers, rebalance, concentration warnings.' }, +]; + +// External links. Real destinations only. +const involve = [ + { label: 'GitHub', href: 'https://github.com/warmbly/warmbly', note: 'Source, issues, pull requests.', icon: 'arrowUpRight' }, + { label: 'Developers', href: '/developers/', note: 'API, webhooks, scopes, idempotency.', icon: 'arrowRight' }, + { label: 'Roadmap', href: '/roadmap/', note: 'Shipped, in progress, researching.', icon: 'arrowRight' }, + { label: 'Changelog', href: '/changelog/', note: 'Every release, dated, plain wording.', icon: 'arrowRight' }, ]; --- - -
-
- -
-
- -
+ +
+ -
-
About
-

- We are building the platform we wished existed. +
+
+ + About + + Open source. Apache 2.0. Public roadmap. +
+ +

+ The cold outreach
platform you can read.

-

- Warmbly was started after one too many quarters watching deliverability collapse on tools that rewarded sending more, not sending well. +

+ Warmbly is the open-source alternative to the closed-source warmup and cold outreach suites. Same surface area, fewer black boxes. The whole engine is on GitHub.

+ +

- -
-
-
Why we built this
-
+ +
+
+
+
+
+
warmbly / warmbly
+ An open-source email warmup and cold outreach platform. +
+ + + public + +
+ +
+ {facts.map((f) => ( +
+
{f.k}
+
{f.v}
+
{f.why}
+
+ ))} +
+
+
+ github.com/warmbly/warmbly +
+
+
+ + + +
+
+
+
Why Warmbly exists
+

+ Cold outreach deserves an open engine. +

+
+ +

- We ran outbound at agencies, growth teams and founder-led startups. Every six months we hit the same wall: - a tool that started healthy, then quietly started landing 30%, 50%, 80% of mail in spam. + For years, the dominant tools in this category have charged near a hundred dollars per inbox, per month, while keeping their warmup logic, sending heuristics, and abuse-detection thresholds completely closed. You paid for throughput. You did not get to see how decisions were made on your behalf.

-
- The tools wouldn't tell us until it was catastrophic. Their warmup pools were silently mixed with trial - mailboxes. Their workers concentrated too much volume on one IP. +
+ When deliverability breaks on a closed platform, you cannot read the code that broke it. You can only ask support, then wait.

- We rebuilt the pieces ourselves. Warmup. Sending. The inbox. The analytics. We shipped them as one - product so the signals couldn't be ignored. + We started Warmbly to remove that opacity. The control plane, the workers, the warmup pools, the encryption model, the quarantine bands. Everything that decides whether your mail lands in the inbox or the spam folder lives in a public repository under Apache 2.0.

- We act on the warning band, not the catastrophe band. We quarantine before mailbox providers do. We - publish the thresholds we use. We keep the platform open source so anyone can verify what it is - actually doing. + The positioning is straightforward. Warmbly is the only credible open-source alternative in cold outreach. Founders, agencies, sales teams, recruiters, and fundraisers can read the engine, run it on their own infrastructure if they want, and stop paying per-inbox tax to closed competitors.

- That product is Warmbly. + We are a small team with a public roadmap. We do not hide what we are working on. We do not invent metrics. We do not pretend to be larger than we are. The product gets better in public.

- -
+ + +
-
-
-
What we believe
-

- Six rules. Hard to bend. -

-
-
- {beliefs.map(([t, b], i) => ( -
-
{String(i + 1).padStart(2, '0')}
-
-
{t}
-

{b}

+
+
Platform shape
+

+ Two planes. One rule per lane. +

+

+ The backend decides. The workers execute. They communicate through Kafka and nothing else. This separation is the reason we can scale send volume by adding small workers instead of growing one giant runtime. +

+
+ +
+ {planes.map((p) => ( +
+
+
+
+
{p.tag}
+ + {p.parts.length} components +
+
{p.title}
+

{p.body}

+
+ {p.parts.map((part) => ( + + {part} + + ))} +
+
+
+ ))} +
+ + +
+
+ Kafka spine + Per-worker command and result topics. Replayable. The only thing connecting the two planes. +
+ worker.commands.* · worker.results.* +
+
+
+ + + +
+
+
+
Build philosophy
+

+ Six rules we will not bend. +

+

+ These are not marketing values. They are constraints written into the codebase and the migrations. If a feature breaks one of them, we do not ship it. +

+
+ +
+ {principles.map((p) => ( +
+
+ {p.n} +
+
{p.title}
+

{p.body}

+
+
+
+ ))} +
+
+
+ + + +
+
+
+
What is different
+

+ Open engine versus closed engine. +

+

+ A factual comparison of posture, not a feature audit. Other tools in this category may match individual capabilities. None of them open up the engine. +

+
+ +
+
+
+
+ + Warmbly +
+
+ + Closed-source suites +
+
+ {positioning.map((row, i) => ( +
+
{row.label}
+
{row.warmbly}
+
{row.closed}
+
+ ))} +
+ +

+ Comparison reflects how Warmbly is built. We do not speak for any other vendor's roadmap or pricing. +

+
+
+ + + +
+
+
+
Repo surface
+

+ What is in the repository. +

+

+ The full platform, top-level. Backend, consumer, workers, tracking, realtime, web, and the encryption primitives that hold them together. +

+
+ +
+
+ warmbly/warmbly · main + {surface.length} top-level paths +
+
+ {surface.map((s) => ( +
+
{s.path}
+
{s.desc}
))}
@@ -96,28 +395,85 @@ const beliefs = [
- -
+ + +
+
+
+
The team
+

+ Small team. Public roadmap. Open source. +

+
+ +
+

+ We are a small group of engineers. We will not pretend to be a global enterprise. Issues on GitHub are read by the people writing the code. Contact emails reach a person, not a queue. +

+

+ Because the team is small, the constraints we put on the codebase have to do the heavy lifting. Safe defaults, conservative warmup, layered abuse controls, encryption everywhere we touch user secrets. We would rather ship one fewer feature than ship a feature that puts your sending reputation at risk. +

+

+ You can follow the work in public. Open issues, pull requests, the roadmap board, the changelog. Everything that matters is visible. +

+ + +
+
+
+ + + +
-
More about us
+
+
Get involved
+

+ Read the engine. Run the engine. Improve the engine. +

+
+
- {[ - { label: 'GitHub', href: 'https://github.com/warmbly/warmbly', note: 'Source on GitHub' }, - { label: 'Brand', href: '/brand/', note: 'Logos and colors' }, - { label: 'Trust', href: '/trust/', note: 'Security and compliance' }, - { label: 'Contact', href: '/contact/', note: 'Reach the team' }, - ].map((l) => ( - + {involve.map((l) => ( +
{l.label}
-
{l.note}
+
{l.note}
- +
))}
- + + diff --git a/site/src/pages/acceptable-use.astro b/site/src/pages/acceptable-use.astro index 14fc4191..713c0ed3 100644 --- a/site/src/pages/acceptable-use.astro +++ b/site/src/pages/acceptable-use.astro @@ -1,76 +1,551 @@ --- -import Legal from '../layouts/Legal.astro'; +import Layout from '../layouts/Layout.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; +import Icon from '../components/Icon.astro'; +import CTA from '../components/CTA.astro'; + +const lastUpdated = '2026-05-27'; + +// Sibling legal documents for cross-linking. +const docs = [ + { label: 'Terms of Service', href: '/terms/' }, + { label: 'Privacy Policy', href: '/privacy/' }, + { label: 'Data Processing Addendum', href: '/dpa/' }, + { label: 'Acceptable Use', href: '/acceptable-use/' }, + { label: 'Subprocessors', href: '/subprocessors/' }, + { label: 'Cookies', href: '/cookies/' }, +]; +const path = Astro.url.pathname; + +// Top-of-page rule strip. "Warmbly is for ..." lines. +const ruleStrip = [ + 'Legitimate B2B cold outreach.', + 'Warmup that protects shared reputation.', + 'Reply handling on mailboxes you own.', + 'Transactional follow-up to people you actually do business with.', +]; + +// Sidebar table of contents. +const toc = [ + { id: 'scope', label: '1. Scope' }, + { id: 'for', label: '2. What Warmbly is for' }, + { id: 'allowed', label: '3. Allowed vs not allowed' }, + { id: 'content', label: '4. Prohibited content' }, + { id: 'targeting', label: '5. Prohibited senders and targeting' }, + { id: 'law', label: '6. Compliance with email law' }, + { id: 'auth', label: '7. Authentication and identification' }, + { id: 'suppression', label: '8. Suppression and unsubscribe' }, + { id: 'volume', label: '9. Volume and rate' }, + { id: 'pool', label: '10. Warmup pool integrity' }, + { id: 'security', label: '11. Security and integrity' }, + { id: 'enforcement', label: '12. Enforcement' }, + { id: 'reporting', label: '13. Reporting abuse' }, + { id: 'changes', label: '14. Changes to this policy' }, +]; + +// Allowed / Not allowed two-column. +const allowed = [ + 'Cold outreach to business addresses with a documented, plausible reason for contact.', + 'Personalized one-to-one prospecting in low daily volumes per mailbox.', + 'Warmup traffic through pools you are entitled to use.', + 'Reply handling and follow-up on conversations you actually started.', + 'Transactional and operational mail to people you have a relationship with.', + 'Honoring opt-outs immediately and keeping a clean suppression list.', + 'Running multiple healthy mailboxes you own, each within its own budget.', +]; +const notAllowed = [ + 'Mass-marketing to consumer addresses sourced without consent.', + 'Sending to scraped or purchased lists you cannot justify recipient by recipient.', + 'Dating, romance scams, sextortion, MLM, crypto pump-and-dump, or other high-abuse verticals.', + 'Forging sender identity or pretending to be a person, company, or Warmbly.', + 'Circumventing per-mailbox budgets, warmup posture, or platform rate limits.', + 'Coordinating distributed bulk-sending across accounts or mailboxes you do not own.', + 'Probing, scraping, or stress-testing the platform without written consent.', +]; + +// Enforcement ladder. Mirrors the mailbox health states in CLAUDE.md. +const enforcement = [ + { + state: 'healthy', + chip: 'bg-emerald-50 text-emerald-700 ring-emerald-100', + dot: 'bg-emerald-500', + title: 'Healthy', + body: 'Default state. Mailbox is participating in normal sending and warmup. No action required.', + }, + { + state: 'watch', + chip: 'bg-sky-50 text-sky-700 ring-sky-100', + dot: 'bg-sky-500', + title: 'Watch', + body: 'Early negative signals such as rising spam placement or borderline complaint rate. Volume guidance tightens and monitoring increases.', + }, + { + state: 'throttled', + chip: 'bg-amber-50 text-amber-700 ring-amber-100', + dot: 'bg-amber-500', + title: 'Throttled', + body: 'Per-mailbox send rate is reduced and pacing is forced. We notify the workspace owner with the reason and the criteria for recovery.', + }, + { + state: 'quarantined', + chip: 'bg-orange-50 text-orange-700 ring-orange-100', + dot: 'bg-orange-500', + title: 'Quarantined', + body: 'Mailbox is removed from shared warmup pools and held in a recovery state. Active sequences for that mailbox may be paused.', + }, + { + state: 'blocked', + chip: 'bg-rose-50 text-rose-700 ring-rose-100', + dot: 'bg-rose-500', + title: 'Blocked', + body: 'Mailbox or workspace is suspended from sending. Re-entry requires review. For workspace-level abuse, the account can be terminated.', + }, +]; --- - -

- Cold email is a privilege. The Warmbly platform shares infrastructure across thousands of senders, and one misbehaving sender can hurt placement for every other paying customer in the same warmup pool. This Acceptable Use Policy ("AUP") sets out the conduct we require. -

+ +
+ -

1. Prohibited content

-

You may not use the service to send mail that:

-
    -
  • Is fraudulent, deceptive, or promotes phishing, money mules, or payment scams.
  • -
  • Impersonates a person, brand or organisation you have no authority to represent.
  • -
  • Promotes illegal activity, including controlled substances, illegal gambling, illegal weapons, malware or hacking services.
  • -
  • Targets minors with marketing.
  • -
  • Contains sexually explicit content or solicits sexual services.
  • -
  • Promotes hate, violence, or discrimination on the basis of protected characteristics.
  • -
  • Distributes copyrighted material without the rights holder's permission.
  • -
  • Contains malware, exploit code, or links to known phishing sites.
  • -
+
+
+ + Legal + + Acceptable Use Policy +
-

2. Prohibited sending behaviour

-
    -
  • Sending to purchased or scraped lists where you have no documented basis to contact the recipient.
  • -
  • Sending to recipients who have asked you to stop.
  • -
  • Sending bulk consumer marketing without consent in jurisdictions where consent is required.
  • -
  • Forging sender identity, bypassing authentication, or sending from a domain you don't own.
  • -
  • Sending high-volume mail from a single mailbox in violation of platform caps.
  • -
  • Attempting to evade reputation enforcement by churning mailboxes after quarantine.
  • -
  • Aggregating multiple customer workspaces under one account to bypass billing.
  • -
+

+ Acceptable use. +

+

+ Warmbly is built for legitimate B2B cold outreach and the warmup that protects it. It is not built for spam, scams, scraped consumer lists, or anything that puts shared sender reputation at risk. These rules apply to every customer, every workspace, every mailbox. +

-

3. Required practices

-
    -
  • Configure SPF, DKIM and DMARC correctly for every sending domain.
  • -
  • Include a real physical address in your sender profile.
  • -
  • Provide a clear opt-out mechanism (reply or one-click) in every message that requires one.
  • -
  • Honour opt-outs immediately, not "within 10 days".
  • -
  • Maintain a documented basis for contacting each recipient (legitimate interest, consent, existing relationship).
  • -
  • Use plain text or minimal HTML; avoid image-only bodies and tracking-pixel-only campaigns.
  • -
  • Send at sensible volumes within platform caps; do not attempt to override them.
  • -
+
+ + + Last updated {lastUpdated} + + + Enforcement ladder + + + + abuse@warmbly.com + +
+
+
-

4. Platform safety

-
    -
  • Do not probe, scan, stress-test or attempt to penetrate the platform without our written consent.
  • -
  • Do not interfere with other customers' use of the service.
  • -
  • Do not use the API to scrape data you would not otherwise be entitled to access.
  • -
  • Do not attempt to identify other customers' contacts, workspaces or accounts.
  • -
+ +
+
+
+
+ Warmbly is for +
+ {ruleStrip.map((line) => ( +
+ + {line} +
+ ))} +
+
+
-

5. Enforcement

-

- Violations of this AUP may result in: -

-
    -
  • Increased monitoring of the relevant mailbox or workspace.
  • -
  • Throttling of send rate.
  • -
  • Quarantine of mailboxes from shared pools.
  • -
  • Suspension of the workspace pending review.
  • -
  • Termination of the account.
  • -
  • In serious cases, reporting to law enforcement or affected third parties.
  • -
-

- Where reasonable, we will give notice and an opportunity to cure. Where the behaviour threatens shared infrastructure (high complaint rates, fraud, abuse signals), we may act immediately. -

+ +
+
-

6. Report a violation

-

- To report suspected abuse, email abuse@warmbly.com. Include the relevant message headers if you can. -

- + + + + +
+ + +
+
Section 1
+

Scope

+
+

+ This Acceptable Use Policy applies to everyone who uses Warmbly. That includes account owners, team members, integrators, and anyone sending mail through a mailbox connected to a Warmbly workspace. It applies to the API, the dashboard, and any future surface we ship. +

+

+ The rules in this document sit on top of the Terms of Service. If something here looks looser than the Terms, the Terms win. If something here looks stricter, the stricter rule applies. +

+
+
+ + +
+
Section 2
+

What Warmbly is for

+

+ Warmbly is a control plane for serious B2B email programs. The intended use cases are narrow and named. +

+
+
+
+ +
+
Legitimate B2B cold outreach
+

Personalized prospecting to business addresses with a real reason for contact.

+
+
+
+ +
+
Warmup
+

Gradual, paced warmup traffic on pools you are entitled to use.

+
+
+
+ +
+
Reply handling
+

Threaded follow-up on conversations you actually started.

+
+
+
+ +
+
Transactional follow-up
+

Operational mail to people you actually do business with.

+
+
+

+ Anything outside that envelope is out of scope. Warmbly is not a newsletter platform, not a consumer marketing tool, and not a way to evade sending limits on platforms that already told you no. +

+
+ + +
+
Section 3
+

Allowed vs not allowed

+

+ A plain-English summary. The detailed rules in later sections govern in case of conflict. +

+ +
+ +
+
+ + + + Allowed +
+
    + {allowed.map((line) => ( +
  • + + {line} +
  • + ))} +
+
+ + +
+
+ + + + Not allowed +
+
    + {notAllowed.map((line) => ( +
  • + + {line} +
  • + ))} +
+
+
+
+ + +
+
Section 4
+

Prohibited content

+

+ You may not use Warmbly to create, send, link to, or distribute any of the following. Lists like this are never exhaustive. Use judgment. If a category fits the spirit of what is listed here, treat it as prohibited. +

+
    +
  • Content that is illegal in the sender's jurisdiction or in the recipient's.
  • +
  • Sexual content involving minors, in any form, no exceptions.
  • +
  • Malware, exploit code, ransomware, credential harvesters, or links to any of the above.
  • +
  • Phishing kits or content designed to deceive a recipient into giving up credentials, payment information, or access.
  • +
  • Targeted harassment, threats, stalking, or doxxing of individuals.
  • +
  • Content that incites violence or hatred against people based on race, ethnicity, religion, gender, sexual orientation, disability, or other protected characteristics.
  • +
  • Content designed to defraud the recipient, including fake invoices, fake job offers, and impersonation of trusted brands.
  • +
  • Content that violates intellectual property rights you do not hold.
  • +
+
+ + +
+
Section 5
+

Prohibited senders and targeting

+

+ Some sending behavior is harmful regardless of the message body. The following patterns are prohibited because they reliably generate complaints, damage shared reputation, or are used as cover for fraud. +

+
    +
  • Sending to scraped consumer lists, purchased lists, or any list where you cannot point to a per-recipient basis for contacting that specific person.
  • +
  • Mass marketing to non-business addresses such as personal Gmail, Yahoo, Outlook, or ISP mailboxes.
  • +
  • Dating, hookup, romance, or sextortion mail. This includes mail that pretends to be from a real or fake person seeking a relationship.
  • +
  • MLM recruitment, crypto pump-and-dump, get-rich-quick offers, fake investment opportunities, and similar high-abuse verticals.
  • +
  • Sending on behalf of clients in any of the above categories, even if your own brand looks legitimate.
  • +
  • Reactivating recipients who have already opted out, asked to be removed, or marked your prior mail as spam.
  • +
  • Splitting one campaign across many workspaces or many mailboxes to dodge per-mailbox limits.
  • +
+
+ + +
+
Section 6
+

Compliance with email law

+
+

+ You are responsible for sending in compliance with the laws that apply to you and to your recipients. Warmbly does not relieve you of that responsibility, and we will not run mail that obviously breaks the law. +

+

+ Depending on jurisdiction and audience, that may include CAN-SPAM in the United States, GDPR in the European Union, CASL in Canada, and the ePrivacy regime in the European Union. The specific obligations vary by law, by recipient location, and by message type. If you are unsure, talk to a lawyer who understands the regime that applies to your sending. +

+

+ At minimum, every commercial message you send through Warmbly must accurately identify the sender, must not use deceptive subject lines or headers, and must give the recipient a clear way to stop receiving mail from you. +

+
+
+ + +
+
Section 7
+

Authentication and identification

+

+ You must send as yourself, from infrastructure you are entitled to use, with the authentication that mailbox providers expect. +

+
    +
  • Real sender identity. The name, company, and address in your messages must match the person or business actually sending them.
  • +
  • SPF, DKIM, and DMARC must be configured for every domain you send from. We will warn you if they are not, and we may refuse to send if they remain broken.
  • +
  • No impersonation of other people, brands, or organizations. No pretending mail is from someone it is not from.
  • +
  • No impersonation of Warmbly. Do not present yourself as a Warmbly employee, do not use the Warmbly name to vouch for your sending, and do not spoof internal Warmbly addresses.
  • +
  • Use mailboxes you legitimately control. Connecting a mailbox you do not own, or whose owner has not authorized your use, is a hard violation.
  • +
+
+ + +
+
Section 8
+

Suppression and unsubscribe

+
+

+ Opt-outs are not negotiable. If a recipient asks you to stop, you stop. The platform enforces suppression automatically, but the policy is independent of the enforcement. +

+

+ For marketing and bulk mail where the applicable law requires it, you must provide a working one-click unsubscribe, and you must honor it. For one-to-one cold outreach, a clear opt-out instruction in the body of the message is the minimum. +

+

+ You may not remove a contact from your suppression list to retry them later. You may not move a suppressed contact between workspaces to evade suppression. You may not buy enrichment data that re-introduces the same person under a different address and treat that as a fresh contact. +

+
+
+ + +
+
Section 9
+

Volume and rate

+

+ Warmbly enforces per-mailbox volume budgets, minimum spacing between sends, and a gradual warmup ramp. Those defaults exist to protect deliverability for you and for everyone else on shared infrastructure. +

+
    +
  • You must respect the warmup posture of each mailbox. A fresh mailbox is not allowed to jump straight to high cold-outreach volume, no matter how confident you are in your list.
  • +
  • You may not bypass or trick per-mailbox daily caps and per-send spacing rules.
  • +
  • You may not coordinate distributed bulk-sending across mailboxes or accounts you do not own. Renting other people's mailboxes to spread volume is not allowed.
  • +
  • You may not split a single underlying campaign across many separate workspaces to dodge our caps or our enforcement.
  • +
  • Spinning up new mailboxes after quarantine to keep the same campaign running is treated as evasion.
  • +
+
+ + +
+
Section 10
+

Warmup pool integrity

+

+ Shared warmup pools depend on every participant behaving like a real mailbox. Abuse here hurts every paying customer in the same pool, so the rules are strict. +

+
    +
  • No forging or tampering with warmup verification tokens.
  • +
  • No synthetic engagement patterns designed to inflate scores or fake reputation.
  • +
  • No using the shared paid pool from mailboxes that have been quarantined or are otherwise unhealthy.
  • +
  • No mixing low-trust mailboxes into the paid pool to inflate volume.
  • +
  • No scripted automation that imitates pool participation from outside the platform.
  • +
+
+ + +
+
Section 11
+

Security and integrity

+

+ You must not attack the platform, the people who work on it, or the customers who use it. Coordinated testing of our security is welcome through the contact in our trust center. Anything else is out of bounds. +

+
    +
  • No probing, scanning, or vulnerability testing of Warmbly infrastructure without prior written consent.
  • +
  • No denial-of-service attempts against the API, the dashboard, the tracking edge, or any other component.
  • +
  • No scraping of platform internals, other workspaces, other customers, or other customers' contacts.
  • +
  • No credential stuffing, brute-forcing, or automated trial of credentials against imported mailboxes or third-party providers from Warmbly infrastructure.
  • +
  • No interference with other customers' sending, warmup, or reply handling.
  • +
  • No reverse-engineering of rate limits or abuse controls in order to defeat them.
  • +
+
+ + +
+
Section 12
+

Enforcement

+

+ Enforcement runs on the same mailbox health states the platform uses internally. Most action happens at the mailbox level. Workspace-level action follows when the pattern is broader or the abuse is severe. +

+ +
+ {enforcement.map((s, i) => ( +
+
+ + + {s.state} + +
+
+
{s.title}
+

{s.body}

+
+
+ ))} +
+ +
+

+ Where reasonable, we give notice and time to fix the problem. Where the behavior is actively damaging shared infrastructure, threatens other customers, or is clearly fraudulent, we act first and explain after. +

+

+ Serious or repeated violations can result in immediate workspace suspension, account termination, refusal to do business in the future, and where appropriate, reporting to law enforcement or affected third parties. +

+
+
+ + +
+
Section 13
+

Reporting abuse

+ +
+
+
+ +
+
+
Saw something? Email us.
+

+ To report a suspected violation by a Warmbly customer, send the message and full headers to abuse@warmbly.com. We acknowledge reports within one business day and act on them based on the evidence, not based on who reported. +

+

+ Security issues belong at security@warmbly.com. Privacy questions belong at privacy@warmbly.com. +

+
+
+
+
+ + +
+
Section 14
+

Changes to this policy

+
+

+ This policy evolves as the platform and the abuse landscape change. We will update it as needed. Material changes are announced by email to workspace owners and by in-app notice. Minor clarifications and typo fixes can land without notice. +

+

+ The current version is always at this URL, with a last-updated date at the top. Continued use of Warmbly after a change takes effect counts as acceptance of the change. +

+
+ +
+ Last updated {lastUpdated} + Questions? legal@warmbly.com +
+
+
+
+
+ + + diff --git a/site/src/pages/brand.astro b/site/src/pages/brand.astro index d556669d..59246321 100644 --- a/site/src/pages/brand.astro +++ b/site/src/pages/brand.astro @@ -1,142 +1,721 @@ --- import Layout from '../layouts/Layout.astro'; -import Cloud from '../components/Cloud.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; import Logo from '../components/Logo.astro'; import Icon from '../components/Icon.astro'; +import CTA from '../components/CTA.astro'; +/* ---------------------------------------------------------------- + PALETTE · the sky ramp + neutral system used across the marketing + surface. Each step is rendered as a real specimen below. +---------------------------------------------------------------- */ const palette = [ - { name: 'sky-1', hex: '#f0f9ff', text: 'dark' }, - { name: 'sky-2', hex: '#e0f2fe', text: 'dark' }, - { name: 'sky-3', hex: '#bae6fd', text: 'dark' }, - { name: 'sky-4', hex: '#7dd3fc', text: 'dark' }, - { name: 'sky-5', hex: '#38bdf8', text: 'light' }, - { name: 'sky-6', hex: '#0284c7', text: 'light' }, - { name: 'sky-7', hex: '#0369a1', text: 'light' }, - { name: 'sky-8', hex: '#075985', text: 'light' }, - { name: 'sky-9', hex: '#0c4a6e', text: 'light' }, - { name: 'sky-10', hex: '#0a3d5c', text: 'light' }, + { name: 'sky-50', token: 'sky-50', hex: '#f0f9ff', text: 'dark' }, + { name: 'sky-100', token: 'sky-100', hex: '#e0f2fe', text: 'dark' }, + { name: 'sky-200', token: 'sky-200', hex: '#bae6fd', text: 'dark' }, + { name: 'sky-300', token: 'sky-300', hex: '#7dd3fc', text: 'dark' }, + { name: 'sky-400', token: 'sky-400', hex: '#38bdf8', text: 'light' }, + { name: 'sky-500', token: 'sky-500', hex: '#0ea5e9', text: 'light' }, + { name: 'sky-600', token: 'sky-600', hex: '#0284c7', text: 'light', role: 'Primary' }, + { name: 'sky-700', token: 'sky-700', hex: '#0369a1', text: 'light', role: 'Hover' }, + { name: 'sky-800', token: 'sky-800', hex: '#075985', text: 'light' }, + { name: 'sky-900', token: 'sky-900', hex: '#0c4a6e', text: 'light' }, +]; + +/* Index of the swatch that is the primary brand color, used for the + bracket marker in the swatch strip. Hoisting comparisons keeps the + Astro JSX parser away from `<` inside `{}` blocks. */ +const primaryIdx = palette.findIndex(p => p.role === 'Primary'); +const hoverIdx = palette.findIndex(p => p.role === 'Hover'); +const isLightStep = palette.map(p => p.text === 'light'); + +const neutrals = [ + { name: 'Foreground', hex: '#0f172a', sub: 'slate-900', cls: 'text-white' }, + { name: 'Heading', hex: '#0f172a', sub: 'slate-900', cls: 'text-white' }, + { name: 'Muted', hex: '#64748b', sub: 'slate-500', cls: 'text-white' }, + { name: 'Border', hex: '#e2e8f0', sub: 'slate-200', cls: 'text-foreground ring-1 ring-[color:var(--border)]' }, + { name: 'Surface 1', hex: '#f5f8fc', sub: 'page bands', cls: 'text-foreground ring-1 ring-[color:var(--border)]' }, + { name: 'Surface 0', hex: '#ffffff', sub: 'card', cls: 'text-foreground ring-1 ring-[color:var(--border)]' }, +]; + +/* ---------------------------------------------------------------- + VOICE · four short principles. No slop. +---------------------------------------------------------------- */ +const voice = [ + { + n: '01', + title: 'Plain text wins.', + body: 'No promo wrappers. No tracked images by default. Cold mail should look like a real message from a real person, because that is what it is.', + }, + { + n: '02', + title: 'Defaults are conservative.', + body: 'Fifty sends a day. Ten minutes between messages. Warmup starts at ten. The defaults are where a healthy mailbox lives, not a starting line to sprint from.', + }, + { + n: '03', + title: 'Numbers must be earned.', + body: 'No fabricated open rates. No screenshot leaderboards. If a metric is on the page it came from your account or the open-source repo.', + }, + { + n: '04', + title: 'Workers stay small.', + body: 'We talk about workers like servers, not heroes. One worker, one IP, a few mailboxes. Volume scales by adding workers, not by overloading one.', + }, +]; + +/* ---------------------------------------------------------------- + DO / DONT cards for the wordmark + mark +---------------------------------------------------------------- */ +const dos = [ + { title: 'Clear space', body: 'Keep at least the height of the mark as padding on every side.' }, + { title: 'Sky on white', body: 'Use the foreground mark on white surfaces. Wordmark in slate-900.' }, + { title: 'White on sky', body: 'On any sky-700 to sky-900 background, switch to the white mark.' }, + { title: 'Mono on photos', body: 'On photography or video, use the solid white or solid foreground variant.' }, +]; + +const donts = [ + { title: 'Do not stretch', body: 'The wordmark and mark are fixed proportion. Never squash or skew.' }, + { title: 'Do not recolor', body: 'Use the foreground, white, or sky-900 variants. No off-brand color fills.' }, + { title: 'Do not add effects', body: 'No drop shadows, no glows, no outlines, no gradients applied to the mark itself.' }, + { title: 'Do not lock up', body: 'Do not combine the Warmbly mark with another logo into a single wordmark.' }, +]; + +/* ---------------------------------------------------------------- + ASSET DOWNLOADS · the marketing site links to stubs. Real files + live on the static CDN under /brand. Hrefs may 404 today. +---------------------------------------------------------------- */ +const assets = [ + { label: 'Wordmark · SVG', href: '/brand/warmbly-wordmark.svg', size: 'SVG' }, + { label: 'Wordmark · PNG · 2x', href: '/brand/warmbly-wordmark@2x.png', size: 'PNG' }, + { label: 'Mark · SVG', href: '/brand/warmbly-mark.svg', size: 'SVG' }, + { label: 'Mark · PNG · 2x', href: '/brand/warmbly-mark@2x.png', size: 'PNG' }, + { label: 'Wordmark · white · SVG', href: '/brand/warmbly-wordmark-white.svg', size: 'SVG' }, + { label: 'Mark · white · SVG', href: '/brand/warmbly-mark-white.svg', size: 'SVG' }, ]; --- - -
-
- -
+ +
+ -
-
Brand
-

- Marks, colors, type. +
+
+ + Brand + + Warmbly's visual identity +
+ +

+ Marks, colors,
type, voice.

-

- Please do not imply partnership without permission. Otherwise these assets are free for press, announcements and integrations. +

+ Everything you need to write about Warmbly, build an integration, or place the logo on a slide. Free to use without asking, with one rule. Do not imply a partnership we have not agreed to.

-

- -
+ +
-
Marks
-

Wordmark and standalone mark.

+
-
-
-
- - Warmbly + +
+
+
Primary lockup
+ Wordmark + mark, on white +
+
-
-
- - Warmbly + + +
+ + + + +
+ + Warmbly +
+ + +
+ Mark height: 1x + · + Wordmark cap-height: 1x + · + Gap: 0.4x
-
- -
-
- -
-
-
-
Wordmark · light
-
Wordmark · dark
-
Mark · light
-
Mark · dark
+ +
+
+
Min width
+
96px
+
+
+
Mark min
+
24px
+
+
+
Clear space
+
1x mark height
+
+
+
Format
+
SVG · PNG
+
+
- -
+ +
-
Palette
-

Sky ramp. Neutral system.

-

10 steps from atmosphere to deep sea. Plus a small neutral system.

+
+
+
Marks
+

+ Four official lockups. +

+

+ Wordmark and standalone mark, each in a light and dark variant. Use the variant with the highest contrast against the background. Nothing else is an approved usage. +

+
    +
  • Wordmark for sign-off, footers, partner pages.
  • +
  • Mark for favicons, avatars, app tiles, OG cards.
  • +
  • SVG everywhere it is supported. PNG at 2x for raster only.
  • +
+
-
- {palette.map((c) => ( -
-
{c.name}
-
{c.hex}
-
- ))} -
+
+ +
+ +
+
+
+ + Warmbly +
+
+
+
Wordmark · light
+ SVG +
+
-
- {[ - { name: 'Foreground', hex: '#0a0f1e', cls: 'text-white' }, - { name: 'Surface', hex: '#ffffff', cls: 'text-foreground ring-1 ring-[color:var(--border)]' }, - { name: 'Muted', hex: '#f6f8fc', cls: 'text-foreground ring-1 ring-[color:var(--border)]' }, - { name: 'Border', hex: '#e6ebf2', cls: 'text-foreground ring-1 ring-[color:var(--border)]' }, - ].map((n) => ( -
-
{n.name}
-
{n.hex}
+ +
+
+
+ + Warmbly +
+
+
+
Wordmark · dark
+ SVG +
+
+ + +
+
+ +
+
+
Mark · light
+ SVG +
+
+ + +
+
+ +
+
+
Mark · brand
+ SVG +
+
- ))} + + +
+
Size reference
+
+
+ + 16px +
+
+ + 24px +
+
+ + 32px +
+
+ + 48px +
+
+ + 64px +
+
+

+ The mark holds up at 16px because the angles are simple. Below 16px, use the favicon variant in /brand/favicon.ico instead. +

+
+ + +
+
+
Asset manifest
+ + Download all + + +
+
+ {assets.map((a) => ( + +
+ +
{a.label}
+
+ {a.size} +
+ ))} +
+
+
- -
+ +
-
Type
-

Inter for UI. JetBrains Mono for code.

+
+
Palette
+

+ Sky ramp. Plus a neutral system. +

+

+ Ten steps from atmosphere to deep sea. Sky-600 is the primary brand color used on links and action buttons. Sky-700 is the hover state. Everything else is composition. +

+
-
-
-
Display · Inter Semibold · 600 · −0.03em
-
Cold email that lands.
+ +
+
+ {palette.map((c, i) => ( +
+ {c.role && ( + + {c.role} + + )} +
{c.name}
+
+
{c.hex}
+
--{c.token}
+
+
+ ))}
-
-
Heading · Inter Semibold · 600 · −0.025em
-
A platform built so the inbox is the default outcome.
+ + +
+
+ {palette.map((c, i) => ( +
+ {i === primaryIdx && Primary} + {i === hoverIdx && Hover} +
+ ))} +
-
-
Body · Inter Regular · 400
-

- Most cold-email platforms reward you for sending more. We reward you for sending well. Mailbox caps default to 50 / day. Workers are capped by the sum of their mailbox budgets, not an arbitrary global number. +

+ + +
+
Neutral system
+
+ {neutrals.map((n) => ( +
+
{n.name}
+
{n.hex}
+
{n.sub}
+
+ ))} +
+
+ + +
+
+
Light surface composition
+
+ + Connect a mailbox + +
+ Healthy senders stay below 50/day. +
+
+ warmup.pool: premium +
+
+
+
+
Dark surface composition
+
+ + Connect a mailbox + +
+ Healthy senders stay below 50/day. +
+
+ warmup.pool: premium +
+
+
+
+
+
+ + +
+
+
+
Typography
+

+ Inter for everything. System mono for code. +

+

+ One typeface keeps the marketing site, app, and docs in the same voice. Sizes follow a small ramp tuned for tight tracking at large display sizes and relaxed leading at body sizes. +

+
+ + +
+
+
+
Sans
+
400 / 500 / 600 / 700 / 800
+
+
Inter
+

+ Self-hosted via @fontsource/inter. Used for every UI surface, every headline, every body paragraph.

-
-
Mono · JetBrains Mono · 500
-
scheduler.setWorkerCap(worker.id, dailyBudget)
+
+
+
Mono
+
system stack
+
+
ui-monospace
+

+ ui-monospace, 'SF Mono', Menlo, monospace. No web download. Used for code snippets, metric labels, and timestamps. +

+
+
+ + +
+
+
+
Display · 72 / 64 · Semibold · -0.03em
+ +
+
Cold email that lands.
+
+ +
+
+
Heading · 44 / 36 · Semibold · -0.025em
+ +
+
+ A platform built so the inbox is the default outcome. +
+
+ +
+
+
Body · 15.5 · Regular · 1.7 leading
+ +
+

+ Most cold-email platforms reward you for sending more. Warmbly rewards you for sending well. Mailbox caps default to fifty a day. Workers are capped by the sum of their mailbox budgets, not an arbitrary global number. The defaults exist because the inbox is a finite resource. +

+
+ +
+
+
Mono · 13.5 · 1.65 leading
+ +
+
// Schedule a send respecting the per-mailbox cap.
+scheduler.scheduleSend({
+  mailbox: "ben@acme.com",
+  campaign: "cmp_2025_q2",
+  cap:      50,           // emails / day
+  gap:      600,          // seconds between sends
+});
+ + +
+
+
+
+
Voice
+

+ How Warmbly sounds. +

+

+ Plain English. Short sentences. Numbers when they matter. If a line could come from any other SaaS site, rewrite it. +

+
+ +
+ {voice.map((v) => ( +
+
{v.n}
+
{v.title}
+

{v.body}

+
+ ))} +
+
+
+
+ + +
+
+
+
Usage
+

+ Logo guardrails. +

+

+ A few things to do, and a few things to leave alone. The mark is simple on purpose. Effects, recolors, and combo lockups undo that on purpose. +

+
+ +
+ +
+
+ + + +
Do
+
+
+ {dos.map((d, i) => ( +
+
+ {i === 0 && ( +
+ + +
+ )} + {i === 1 && ( +
+ + Warmbly +
+ )} + {i === 2 && ( +
+
+ + Warmbly +
+
+ )} + {i === 3 && ( +
+ +
+ )} +
+
+
{d.title}
+

{d.body}

+
+
+ ))} +
+
+ + +
+
+ + + +
Do not
+
+
+ {donts.map((d, i) => ( +
+
+ + + + {i === 0 && ( +
+ +
+ )} + {i === 1 && ( + + )} + {i === 2 && ( + + )} + {i === 3 && ( +
+ + × +
CO
+
+ )} +
+
+
{d.title}
+

{d.body}

+
+
+ ))} +
+
+
+
+
+ + +
+
+
+ +
+
Naming
+

+ Always Warmbly. One word. Capital W. +

+
+
+ Yes + Warmbly +
+
+ No + warmbly, WARMBLY, Warm.bly, Warm Bly, WarmBly +
+

+ In running prose, refer to the company as Warmbly. Refer to the product as Warmbly or as the Warmbly platform. Avoid possessives on the wordmark itself in logo contexts. +

+
+
+ + +
+
+
Press & partnerships
+

+ Need a quote, a logo file, or an interview? +

+

+ Email is the fastest way to reach us. Include the publication or context and a deadline if you have one. We try to reply within one business day. +

+ +
+ Version 1.0 · Updated +
+
+
+
+
+
+ + diff --git a/site/src/pages/cookies.astro b/site/src/pages/cookies.astro index 72612109..913553b8 100644 --- a/site/src/pages/cookies.astro +++ b/site/src/pages/cookies.astro @@ -1,49 +1,673 @@ --- -import Legal from '../layouts/Legal.astro'; +import Layout from '../layouts/Layout.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; +import Icon from '../components/Icon.astro'; + +/* ----------------------------------------------------------------- + Cookies policy. Full redesign. + + Source of truth: + - CLAUDE.md, sections "Auth and signup protection", "Sharing" + - internal/pkg/captcha/turnstile.go (Cloudflare Turnstile) + - internal/api/middleware/ratelimit.go (session + CSRF surface) + - /subprocessors page (Stripe, Cloudflare) + + Honest posture: Warmbly does not run third-party advertising + trackers and does not currently set marketing or analytics cookies + on the marketing site. Product analytics is server-side and the + workspace owner can disable it from the app. The page says so. + + Tone: no em dashes. No fictional analytics vendors. + + Astro parser quirk: any `<` or `<=` comparison inside `{}` blocks + breaks the parser. Hoist them up here. + ----------------------------------------------------------------- */ + +const LAST_UPDATED = '2026-05-27'; + +// Sidebar TOC. Each entry maps to an in-page anchor on a section. +const toc = [ + { id: 'what-cookies-are', label: 'What cookies are' }, + { id: 'how-we-use', label: 'How Warmbly uses cookies' }, + { id: 'strictly-necessary', label: 'Strictly necessary' }, + { id: 'functional', label: 'Functional' }, + { id: 'analytics', label: 'Analytics' }, + { id: 'marketing', label: 'Marketing' }, + { id: 'third-party', label: 'Third-party cookies' }, + { id: 'choices', label: 'Your choices and controls' }, + { id: 'changes', label: 'Changes to this policy' }, + { id: 'contact', label: 'Contact' }, +]; + +// Cookie type definition used by the table renderer. Centralised so +// every category section uses the same row shape and visual. +type Cookie = { + name: string; + provider: string; + purpose: string; + duration: string; + party: '1st' | '3rd'; + essential?: boolean; +}; + +// Strictly necessary cookies. These are what Warmbly actually sets to +// keep an authenticated session safe. Disabling these breaks login. +const strictlyNecessary: Cookie[] = [ + { + name: 'wb_session', + provider: 'warmbly.com', + purpose: 'Authenticated session cookie. Keeps you signed in to the Warmbly application.', + duration: '30 days, or until sign-out', + party: '1st', + essential: true, + }, + { + name: 'wb_csrf', + provider: 'warmbly.com', + purpose: 'Cross-site request forgery token. Protects state-changing form submissions.', + duration: 'Session', + party: '1st', + essential: true, + }, + { + name: 'wb_workspace', + provider: 'warmbly.com', + purpose: 'Remembers the workspace you were last viewing so you land back in it after sign-in.', + duration: '180 days', + party: '1st', + essential: true, + }, + { + name: 'cf_clearance', + provider: 'cloudflare.com', + purpose: 'Cloudflare bot challenge clearance. Marks your browser as having passed a Turnstile or managed challenge.', + duration: '30 minutes', + party: '3rd', + essential: true, + }, + { + name: '__cf_bm', + provider: 'cloudflare.com', + purpose: 'Cloudflare Bot Management. Distinguishes humans from automated traffic on the edge.', + duration: '30 minutes', + party: '3rd', + essential: true, + }, +]; + +// Functional cookies. UI memory only. Optional but very low impact if +// you turn them off. +const functional: Cookie[] = [ + { + name: 'wb_theme', + provider: 'warmbly.com', + purpose: 'Stores your light or dark theme preference.', + duration: '1 year', + party: '1st', + }, + { + name: 'wb_sidebar', + provider: 'warmbly.com', + purpose: 'Remembers whether the app sidebar is collapsed or expanded.', + duration: '1 year', + party: '1st', + }, + { + name: 'wb_dismissed_notice_*', + provider: 'warmbly.com', + purpose: 'Tracks which one-time in-app notices and onboarding tips you have dismissed.', + duration: '1 year', + party: '1st', + }, +]; + +// Analytics cookies. Be honest: the marketing site does not run a +// third-party analytics cookie today. Product analytics in the app is +// server-side and workspace-owner-controlled. +const analytics: Cookie[] = []; + +// Marketing cookies. Warmbly does not run third-party ad trackers or +// retargeting pixels on the marketing site. +const marketing: Cookie[] = []; + +// Third-party cookies. Only when you reach a page that loads the +// vendor's script. +const thirdParty: Cookie[] = [ + { + name: '__stripe_mid', + provider: 'stripe.com', + purpose: 'Stripe fraud-prevention identifier. Set when you reach a billing or checkout page.', + duration: '1 year', + party: '3rd', + }, + { + name: '__stripe_sid', + provider: 'stripe.com', + purpose: 'Stripe session identifier used during checkout.', + duration: '30 minutes', + party: '3rd', + }, + { + name: 'cf_clearance', + provider: 'cloudflare.com', + purpose: 'Cloudflare challenge clearance. Same cookie as the essential row above, listed here for completeness.', + duration: '30 minutes', + party: '3rd', + }, +]; + +// Overview card stats. Pulled from the actual category counts above so +// the page stays honest when these tables change. +const totalCookies = + strictlyNecessary.length + + functional.length + + analytics.length + + marketing.length + + thirdParty.length; +const essentialCount = strictlyNecessary.length; +const optionalCount = functional.length + analytics.length + marketing.length; +const thirdPartyCount = strictlyNecessary.filter((c) => c.party === '3rd').length + thirdParty.length; + +const overview = [ + { + k: 'Total cookies', + v: String(totalCookies), + note: 'Across every category Warmbly currently sets.', + }, + { + k: 'Strictly necessary', + v: String(essentialCount), + note: 'Needed to keep you signed in and protect form submissions.', + }, + { + k: 'Optional', + v: String(optionalCount), + note: 'Functional preferences only. No analytics or ad cookies today.', + }, + { + k: 'Third-party', + v: String(thirdPartyCount), + note: 'Only Stripe and Cloudflare. No advertising networks.', + }, +]; + +// Category section definitions consumed by the main column. +const categories: { + id: string; + eyebrow: string; + title: string; + intro: string; + rows: Cookie[]; + emptyNote?: string; + tone: 'essential' | 'functional' | 'analytics' | 'marketing'; +}[] = [ + { + id: 'strictly-necessary', + eyebrow: 'Required', + title: 'Strictly necessary cookies', + intro: 'These cookies are required for the Warmbly application to function. They keep you signed in, protect form submissions against forgery, and let Cloudflare distinguish humans from automated traffic. You cannot opt out of these and still use the app.', + rows: strictlyNecessary, + tone: 'essential', + }, + { + id: 'functional', + eyebrow: 'Optional', + title: 'Functional cookies', + intro: 'These cookies remember small interface preferences so the app feels consistent between visits. They do not track you across sites and they do not feed any analytics or advertising system.', + rows: functional, + tone: 'functional', + }, + { + id: 'analytics', + eyebrow: 'Optional', + title: 'Analytics cookies', + intro: 'We do not currently set analytics cookies on the marketing site. Product analytics inside the application is collected server-side, and the workspace owner can disable it from workspace settings. If this changes, this section will list every cookie before it ships.', + rows: analytics, + emptyNote: 'We do not currently set cookies in this category.', + tone: 'analytics', + }, + { + id: 'marketing', + eyebrow: 'Optional', + title: 'Marketing cookies', + intro: 'We do not run advertising trackers, retargeting pixels, or cross-site identifiers. We have no marketing cookies to disclose. This is a deliberate product decision and we intend to keep it that way.', + rows: marketing, + emptyNote: 'We do not currently set cookies in this category.', + tone: 'marketing', + }, +]; + +// Controls card. Each item is a way you can manage cookies for +// Warmbly specifically. +const controls = [ + { + icon: 'shield', + title: 'Browser controls', + body: 'Every modern browser lets you view, block and delete cookies for a single site. Blocking strictly necessary cookies will sign you out of Warmbly.', + }, + { + icon: 'lock', + title: 'Do Not Track', + body: 'Warmbly serves the same product whether or not your browser sends a Do Not Track signal, because we do not run targeted advertising trackers either way.', + }, + { + icon: 'filter', + title: 'Workspace analytics toggle', + body: 'Workspace owners can disable product analytics for the entire workspace from workspace settings. This affects server-side telemetry, not cookies on this site.', + }, + { + icon: 'cpu', + title: 'Third-party scripts', + body: 'Stripe cookies only appear when you reach a billing page. Cloudflare cookies appear when the edge needs to challenge a request. Neither is used for advertising.', + }, +]; + +// Per-category visual tone for the category eyebrow chip. +const toneStyles: Record = { + essential: 'bg-rose-50 text-rose-700 ring-1 ring-rose-100', + functional: 'bg-sky-50 text-sky-700 ring-1 ring-sky-100', + analytics: 'bg-amber-50 text-amber-700 ring-1 ring-amber-100', + marketing: 'bg-violet-50 text-violet-700 ring-1 ring-violet-100', +}; --- - -

- Warmbly uses a small number of cookies. We don't run third-party advertising trackers, and we don't sell cookie data. -

-

Essential cookies

-
    -
  • warmbly_session — your authenticated session for the application. Expires when you log out or after 30 days of inactivity.
  • -
  • warmbly_csrf — protects against cross-site request forgery on form submissions.
  • -
  • warmbly_theme — remembers your light/dark theme choice.
  • -
+ +
+ -

Functional cookies

-
    -
  • warmbly_workspace — last selected workspace, so you land in the right place after login.
  • -
  • warmbly_dismissed_notice_* — remembers which one-time notices you've dismissed.
  • -
+
+
+ + Legal + + Cookies policy +
-

Analytics

-

- We use PostHog for product analytics (workspace-owner-disable-able). PostHog uses a first-party cookie named ph_* to attribute events to a session. We don't share PostHog data with third parties. -

+

+ Cookies. +

-

Third-party cookies

-

- The Warmbly application embeds the following third-party services, each of which may set its own cookies in your browser: -

-
    -
  • Stripe — when you visit billing pages or enter card details. Required to complete payment.
  • -
  • Cloudflare Turnstile — bot-protection CAPTCHA on login, signup and password reset.
  • -
+

+ Warmbly only uses the cookies it needs to keep you signed in, protect your account, and remember a handful of interface preferences. No advertising trackers. No cross-site identifiers. You stay in control. +

-

Managing cookies

-

- Most browsers let you block or delete cookies. Blocking essential cookies will prevent you from staying logged in. You can disable PostHog analytics on a per-workspace basis in workspace settings. -

+
+ + Last updated {LAST_UPDATED} +
+
+
-

Do Not Track

-

- Our application surfaces the same product whether or not your browser sends a Do Not Track signal. We don't use targeted advertising trackers either way. -

-
+ +
+
+
+ + + + + +
+ + +
+
Background
+

+ What cookies are. +

+

+ A cookie is a small piece of text that a website stores in your browser. The browser sends that text back to the same site on later requests. Cookies let the site recognise that two requests came from the same browser, which is how a site keeps you signed in, remembers a setting, or measures a session. Cookies are not executable, they cannot read other files on your computer, and a cookie set by one site cannot be read by an unrelated site. +

+
+ + +
+
Overview
+

+ How Warmbly uses cookies. +

+

+ Warmbly keeps its cookie surface deliberately small. The cookies you see below fall into four buckets: strictly necessary, functional, analytics, and marketing. We currently set cookies in two of those four buckets, and we say so plainly when a category is empty. +

+ +
+
+ {overview.map((o, i) => { + const borderRight = (i % 2) !== 1 ? 'sm:border-r' : ''; + const borderRightLg = (i % 4) !== 3 ? 'lg:border-r' : ''; + const borderBottom = i < (overview.length - 1) ? 'border-b sm:border-b-0' : ''; + const borderBottomSm = i < 2 ? 'sm:border-b lg:border-b-0' : ''; + return ( +
+
{o.k}
+
{o.v}
+
{o.note}
+
+ ); + })} +
+
+
+ + + {categories.map((cat) => ( +
+
+ + {cat.eyebrow} + + {cat.rows.length} {cat.rows.length === 1 ? 'cookie' : 'cookies'} +
+

+ {cat.title} +

+

{cat.intro}

+ + {cat.rows.length === 0 ? ( +
+
+ +
+
+
{cat.emptyNote}
+
If this changes, the table will be populated here before any new cookie is shipped, and the last-updated date at the top of this page will move.
+
+
+ ) : ( + <> + + + + +
+ {cat.rows.map((c) => ( +
+
+ {c.name} + + {c.party === '1st' ? '1st party' : '3rd party'} + +
+ {c.essential && ( +
+ + Essential +
+ )} +

{c.purpose}

+
+
+
Provider
+
{c.provider}
+
+
+
Duration
+
{c.duration}
+
+
+
+ ))} +
+ + )} +
+ ))} + + +
+
+ + Vendors + + {thirdParty.length} cookies +
+

+ Third-party cookies. +

+

+ The Warmbly application uses two third-party vendors that may set cookies in your browser: Stripe for billing and Cloudflare for edge protection. Neither is used for advertising. Each vendor publishes its own cookie policy. +

+ + + + + +
+ {thirdParty.map((c) => ( +
+
+ {c.name} + 3rd party +
+

{c.purpose}

+
+
+
Provider
+
{c.provider}
+
+
+
Duration
+
{c.duration}
+
+
+
+ ))} +
+
+ + +
+
Controls
+

+ Your choices and controls. +

+

+ You can manage cookies at the browser level, at the workspace level for product analytics, or by avoiding the pages that load third-party scripts. Each option below applies to Warmbly specifically. +

+ +
+ {controls.map((c) => ( +
+
+
+ +
+
{c.title}
+
+

{c.body}

+
+ ))} +
+ + + +
+ + +
+
Maintenance
+

+ Changes to this policy. +

+

+ We may update this page when we add, remove, or change a cookie. The last-updated date at the top of the page always reflects the most recent revision. Material changes that affect optional cookies will be announced via email or in-app notice at least 30 days before they take effect. +

+
+ + +
+
Contact
+

+ Contact. +

+

+ Questions about this policy or a specific cookie set by Warmbly? Email + privacy@warmbly.com + and we will respond within 30 days. For a copy of our Data Processing Addendum, see + /dpa. +

+ +
+
Warmbly Labs, Inc. 1209 N. Orange Street, Wilmington, DE 19801, USA.
+
Last updated {LAST_UPDATED}
+
+
+ +
+
+
+
+ diff --git a/site/src/pages/deliverability.astro b/site/src/pages/deliverability.astro index 59561f0c..e7230e1a 100644 --- a/site/src/pages/deliverability.astro +++ b/site/src/pages/deliverability.astro @@ -1,145 +1,880 @@ --- import Layout from '../layouts/Layout.astro'; -import Cloud from '../components/Cloud.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; import Icon from '../components/Icon.astro'; import CTA from '../components/CTA.astro'; +// All numbers and policies on this page are drawn directly from the +// Warmbly codebase and CLAUDE.md operational policy: +// internal/config/constants.go +// internal/scheduler/{campaign,email,warmup}_scheduler.go +// internal/repository/pg_warmup.go +// internal/repository/pg_email.go +// internal/app/worker/wmail/ratelimit.go +// internal/app/consumer/event_new_email.go +// internal/infrastructure/db/migrations/000010_warmup_pools.up.sql + +// ========================================================================= +// Mailbox roster shown in the floating dashboard mock under the hero. +// Each row simulates a per-mailbox health snapshot. No fabricated reply +// rates or open rates. Values reflect the band logic in CLAUDE.md. +// ========================================================================= +const mailboxes = [ + { address: 'ben@acme.com', state: 'healthy', spam: '2%', complaint: '0.00%', bounce: '0.4%', cap: '50 / day', sent: 47, gap: '11m' }, + { address: 'sara@acme.com', state: 'healthy', spam: '4%', complaint: '0.01%', bounce: '0.6%', cap: '50 / day', sent: 42, gap: '10m' }, + { address: 'devon@acme.com', state: 'healthy', spam: '7%', complaint: '0.02%', bounce: '0.9%', cap: '50 / day', sent: 50, gap: '12m' }, + { address: 'priya@acme.io', state: 'watch', spam: '12%', complaint: '0.04%', bounce: '1.4%', cap: '30 / day', sent: 18, gap: '15m' }, + { address: 'felix@acme.io', state: 'throttled', spam: '17%', complaint: '0.07%', bounce: '2.1%', cap: '15 / day', sent: 9, gap: '20m' }, + { address: 'noah@acme.io', state: 'quarantined', spam: '23%', complaint: '0.12%', bounce: '5.6%', cap: 'paused', sent: 0, gap: 'paused' }, + { address: 'old@legacy.dev', state: 'blocked', spam: '44%', complaint: '0.34%', bounce: '11%', cap: 'blocked', sent: 0, gap: 'blocked' }, +]; + +const stateTone = (s: string) => { + if (s === 'healthy') return { dot: 'bg-emerald-500', chip: 'bg-emerald-50 text-emerald-700', ring: 'ring-emerald-200' }; + if (s === 'watch') return { dot: 'bg-amber-500', chip: 'bg-amber-50 text-amber-700', ring: 'ring-amber-200' }; + if (s === 'throttled') return { dot: 'bg-amber-600', chip: 'bg-amber-50 text-amber-800', ring: 'ring-amber-200' }; + if (s === 'quarantined') return { dot: 'bg-rose-500', chip: 'bg-rose-50 text-rose-700', ring: 'ring-rose-200' }; + return { dot: 'bg-rose-700', chip: 'bg-rose-100 text-rose-800', ring: 'ring-rose-300' }; +}; + +// ========================================================================= +// The product defaults table. Real numbers from internal/config/constants.go +// ========================================================================= +const defaults = [ + { k: 'Cold campaign cap', v: '50 / day', why: 'Per-mailbox ceiling enforced server-side. Validated up to 100 max for explicit overrides.' }, + { k: 'Minimum send gap', v: '600s', why: 'Ten minutes between any two cold sends from the same mailbox. Bursts are flattened to this cadence regardless of queue depth.' }, + { k: 'Warmup start', v: '10 / day', why: 'First warmup send volume. Low enough to read as normal individual activity on any provider.' }, + { k: 'Warmup ceiling', v: '40 / day', why: 'Holds here once reached. Crowding the cold-campaign budget gains nothing reputationally.' }, + { k: 'Warmup ramp', v: '+1 / day', why: 'One additional warmup message per business day. Aggressive ramps trip volume-anomaly heuristics on the receiver.' }, + { k: 'Sync burst limit', v: '100 / 5m', why: 'Worker-side. If a mailbox tries to ingest more than 100 new emails in 5 minutes it is rate-limited automatically.' }, + { k: 'Sync hourly limit', v: '500 / hr', why: 'Worker-side. The mailbox is paused upstream if this is breached, before provider-side throttling sets in.' }, + { k: 'Invalid token block', v: '3 / 24h', why: 'Three or more malformed warmup tokens in a 24h window auto-block the mailbox from the pool.' }, + { k: 'Pool spam-score block', v: 'score > 50', why: 'Cumulative pool spam score above 50 removes the mailbox before mailbox providers act.' }, +]; + +// ========================================================================= +// Auto-quarantine bands. CLAUDE.md "Recommended internal policy for shared +// paid pools". Sample floors written below the table. +// ========================================================================= const bands = [ - { provider: 'Google · bulk sender', us: '0.03%', warn: '0.10%', kill: '0.30%' }, - { provider: 'Amazon SES · complaints', us: '0.03%', warn: '0.10%', kill: '0.50%' }, - { provider: 'Amazon SES · bounces', us: '1.0%', warn: '5.0%', kill: '10%' }, - { provider: 'Microsoft 365', us: '0.03%', warn: '0.10%', kill: 'Bulk use disallowed' }, + { + name: 'Healthy', tone: 'emerald', + spam: 'under 10%', complaint: 'under 0.03%', bounce: 'under 1%', + action: 'Default state. Continue ramp. Eligible for pool selection.', + }, + { + name: 'Watch', tone: 'amber', + spam: 'at or above 10%', complaint: 'at or above 0.03%', bounce: 'not used', + action: 'Lower volume, increase spacing, increase monitoring. Still in the pool.', + }, + { + name: 'Quarantined', tone: 'rose', + spam: 'at or above 20%', complaint: 'at or above 0.10%', bounce: 'at or above 5%', + action: 'Removed from the shared paid pool for 7 days. Moves to the recovery state.', + }, + { + name: 'Blocked', tone: 'rose-dark', + spam: 'at or above 40%', complaint: 'at or above 0.30%', bounce: 'at or above 10%', + action: '30-day block. Manual review required for re-entry. Cold sending paused.', + }, + { + name: 'Catastrophic', tone: 'rose-dark', + spam: 'at or above 80%', complaint: 'severe', bounce: 'severe', + action: 'Long-duration block. Full reputation reset workflow. No automatic re-entry.', + }, ]; +const toneClass = (t: string) => { + if (t === 'emerald') return { dot: 'bg-emerald-500', chip: 'bg-emerald-50 text-emerald-700', bar: 'bg-emerald-500' }; + if (t === 'amber') return { dot: 'bg-amber-500', chip: 'bg-amber-50 text-amber-700', bar: 'bg-amber-500' }; + if (t === 'rose-dark') return { dot: 'bg-rose-700', chip: 'bg-rose-100 text-rose-800', bar: 'bg-rose-700' }; + return { dot: 'bg-rose-500', chip: 'bg-rose-50 text-rose-700', bar: 'bg-rose-500' }; +}; + +// ========================================================================= +// Pool isolation surfaces. Free, premium, recovery, dedicated. +// ========================================================================= +const pools = [ + { + name: 'Free pool', accent: '#94a3b8', + when: 'Free trial', + summary: 'Trial mailboxes only. Same auto-quarantine thresholds as premium.', + rules: [ + 'Recipients drawn exclusively from other free-plan mailboxes.', + 'Cannot send to premium-pool mailboxes.', + 'No participation in premium spam-score aggregation.', + ], + }, + { + name: 'Premium pool', accent: '#0284c7', featured: true, + when: 'Starter, Grow, Business', + summary: 'Vetted paid mailboxes only. Stricter quarantine bands, lower tolerance for drift.', + rules: [ + 'Recipients drawn only from other healthy premium mailboxes.', + 'Quarantined senders drop into the recovery pool, never silently back into premium.', + 'Pool spam-score aggregated per-mailbox over rolling windows.', + ], + }, + { + name: 'Recovery pool', accent: '#d97706', + when: 'Automatic on quarantine', + summary: 'Isolated cooldown for senders that breached a band. Not visible to healthy traffic.', + rules: [ + '7-day cooldown after quarantine. 30-day cooldown after a hard block.', + '20-delivery probation required before re-entry to the premium pool.', + 'Authentication and bounce checks must pass on re-entry.', + ], + }, + { + name: 'Dedicated', accent: '#0f172a', + when: 'Enterprise', + summary: 'Isolated worker and IP per organisation. Still participates in the premium pool.', + rules: [ + 'One worker process and IP allocated to one organisation.', + 'Per-mailbox reputation, not an infrastructure shortcut.', + 'Same band thresholds apply. Isolation is not immunity.', + ], + }, +]; + +// ========================================================================= +// Authentication coverage. SPF, DKIM, DMARC, PTR. Mailbox-first checks. +// ========================================================================= const auth = [ - { name: 'SPF', body: 'We verify the sender domain SPF record authorises both the mailbox provider and any third-party services you use to send.' }, - { name: 'DKIM', body: 'We check that DKIM is published, the selector resolves, and the key length is at least 1024 bits.' }, - { name: 'DMARC', body: 'We surface DMARC policy, alignment mode, and the rua reporting address. We warn loudly on p=none.' }, + { name: 'SPF', tag: 'TXT record', + body: 'We resolve the sending domain SPF record and verify both the mailbox provider and any third-party services you send through are authorised.', + fail: 'Missing or misconfigured SPF blocks the mailbox from leaving the connection wizard.' }, + { name: 'DKIM', tag: 'Selector + key', + body: 'We resolve the DKIM selector for the provider, verify the public key parses, and require a key length of 1024 bits or higher.', + fail: 'A missing selector surfaces the exact DNS record to publish, copy-pastable.' }, + { name: 'DMARC', tag: 'Policy + alignment', + body: 'We read the DMARC record, surface the policy, alignment mode, and rua reporting address. p=none is flagged loudly.', + fail: 'Reporting addresses point at a Warmbly inbox so aggregate reports surface inside the dashboard.' }, + { name: 'PTR', tag: 'Reverse DNS', + body: 'For BYO SMTP we resolve the reverse DNS of the sending IP and verify it forward-resolves to the same hostname.', + fail: 'A mismatched PTR is the single fastest path to spam at any commercial receiver.' }, ]; -const signals = [ - ['Inbox vs spam placement', 'Per-mailbox placement tracked through warmup probes and recipient feedback.'], - ['Complaint rate', 'ARF complaints, Gmail FBLs, and "mark as spam" signals from connected mailboxes.'], - ['Bounce rate', 'Hard and soft bounces by mailbox and domain, with reason classification.'], - ['Warmup token health', 'Missing, malformed, or replayed verification tokens count as suspicion.'], - ['Reply detection', 'Recipients replying is the strongest positive deliverability signal we have.'], - ['OOO + auto-replies', 'Classified so they do not pollute reply rate or trigger sequence steps.'], - ['Provider throttling', '4xx and rate-limit responses tracked per mailbox and exposed as health signals.'], - ['Connection abuse', 'Burst syncs (>100 in 5min) and hourly (>500) rate-limit the mailbox automatically.'], +// ========================================================================= +// Suppression engine. Mailbox-first, not list-first. Real codepaths. +// ========================================================================= +const suppression = [ + { trigger: 'Hard bounce', scope: 'workspace', source: 'consumer.deliverability', note: 'The recipient is suppressed across every campaign in the workspace immediately. No future send attempts.' }, + { trigger: 'Soft bounce ×3', scope: 'domain', source: 'consumer.deliverability', note: 'Three soft bounces to the same domain on a mailbox auto-suppress the recipient. Backs off pressure on the receiver.' }, + { trigger: 'Complaint (FBL / ARF)', scope: 'workspace', source: 'consumer.deliverability', note: 'Spam complaints from Gmail FBL or Microsoft SNDS suppress the recipient and increment the mailbox health score.' }, + { trigger: 'One-click unsubscribe', scope: 'workspace', source: 'tracking.unsubscribe', note: 'RFC 8058 List-Unsubscribe-Post compliance. The recipient is suppressed before the dashboard even renders the confirmation.' }, + { trigger: 'Reply: STOP / REMOVE', scope: 'workspace', source: 'consumer.reply.classifier', note: 'Common unsubscribe phrases in inbound replies are detected and auto-suppress without an operator in the loop.' }, + { trigger: 'Manual suppression', scope: 'workspace', source: 'api / dashboard', note: 'Operators can suppress by recipient, domain, or workspace import. CSV uploads are deduplicated against the existing list.' }, +]; + +// ========================================================================= +// Recommended cold sending posture. From CLAUDE.md "Sending Safety Policy" +// and "Recommended operational posture". +// ========================================================================= +const posture = [ + { stage: 'New mailbox', range: '10 to 20 / day', note: 'Fresh or recently connected mailbox. Treat the first weeks as reputation building, not pipeline.' }, + { stage: 'Ramping', range: '+5 / week', note: 'Increase weekly while complaint and bounce rates stay clean. Pull back on any negative signal.' }, + { stage: 'Stable band', range: '30 to 50 / day', note: 'Normal safe range for most cold outreach mailboxes. The repo default cap lives at the top of this band.' }, + { stage: 'Above default', range: 'requires review', note: 'Anything above 50 / day per cold mailbox should require positive reputation signals, low complaint rate, and explicit review.' }, +]; + +// ========================================================================= +// Critique stance. Real, opinionated, no fake competitor names. +// ========================================================================= +const critique = [ + { h: 'Worker-first throughput is the wrong unit.', + b: 'A sending platform that brags about millions per worker is solving the wrong problem. Reputation is per-mailbox, per-domain, per-IP. Cramming volume through a single sender runtime concentrates risk and shortens the life of every mailbox on it.' }, + { h: 'Closed warmup pools without proof are noise.', + b: 'If warmup traffic cannot prove it is warmup traffic, a single bad actor can poison the pool by replying to themselves. The defence is a per-message signed token validated on classification, not a private mailbox list.' }, + { h: 'Acting on provider enforcement is acting too late.', + b: 'When Google or SES throttles your domain, the damage has already shipped. The right thresholds for a shared pool sit weeks earlier, on rolling per-mailbox windows, not on lagging aggregate reports.' }, + { h: 'Free pools and paid pools should never share air.', + b: 'If a trial account can land in the same warmup loop as a paying customer, the trial account is the customer\'s deliverability risk. Pool isolation is not a tier perk, it is a safety boundary.' }, +]; + +// ========================================================================= +// FAQ. Real product behaviour, real thresholds. +// ========================================================================= +const faq = [ + ['Is deliverability per-mailbox or per-account?', + 'Per-mailbox. Every mailbox has its own health score, its own send cap, its own pacing, and its own band membership. An account with five mailboxes can have five different health states at the same time.'], + ['What auto-quarantines a mailbox?', + 'Three or more invalid warmup tokens in 24 hours, a pool spam score above 50, spam-folder placement at or above 20% with at least 20 warmup deliveries in 7 days, a complaint rate at or above 0.10%, or a bounce rate at or above 5%.'], + ['How is suppression applied across campaigns?', + 'Hard bounces, complaints, and unsubscribes are workspace-scoped. Once a recipient is suppressed, every running campaign across every mailbox in the workspace skips them on the next dispatch tick. No future send attempts ever queue.'], + ['Why is the default cap only 50 / day?', + 'Because the safest mailbox-first answer beats the most exciting throughput number. 50 / day with a 600 second gap fits well under provider scrutiny on every major receiver. Anything above that should be earned mailbox-by-mailbox.'], + ['What happens when a mailbox is blocked?', + 'It is removed from the shared paid pool, cold sending is paused or throttled, and the mailbox enters the recovery pool. It must pass 20-delivery probation, authentication checks, and a clean spam-placement window before re-entering the premium pool.'], +]; + +// ========================================================================= +// Astro JSX parser hoists. Any `<` or `<=` inside `{}` will break parsing, +// so we pre-compute everything as data here and iterate cleanly in JSX. +// ========================================================================= + +// Gauge tick marks below the band bar. +const gaugeTicks = [ + { pct: 0, label: '0%' }, + { pct: 10, label: '10%' }, + { pct: 20, label: '20%' }, + { pct: 40, label: '40%' }, + { pct: 80, label: '80%' }, + { pct: 100, label: '100%' }, +]; + +// "By the numbers" strip. Real product defaults only. +const numbers = [ + { v: '50', u: '/ day', l: 'cold cap per mailbox', src: 'internal/config/constants.go' }, + { v: '600', u: 's', l: 'min gap between sends', src: 'internal/scheduler/email_scheduler.go' }, + { v: '10', u: '/ day', l: 'warmup start volume', src: 'internal/scheduler/warmup_scheduler.go' }, + { v: '40', u: '/ day', l: 'warmup ceiling', src: 'internal/scheduler/warmup_scheduler.go' }, + { v: '+1', u: '/ day', l: 'warmup ramp', src: 'internal/scheduler/warmup_scheduler.go' }, + { v: '500', u: '/ hr', l: 'worker sync hourly limit', src: 'internal/app/worker/wmail/ratelimit.go' }, ]; --- - -
-
- -
+ +
+ -
-
Deliverability
-

- Built so the inbox is the default outcome. +
+
+ + Deliverability + + Mailbox-first, not worker-first +
+ +

+ The inbox is
the only outcome that counts.

-

- Inbox placement is not a setting you toggle on. It is the cumulative result of authentication, warmup, volume discipline, content quality and provider-side reputation. We instrument every one of them. +

+ Reputation lives in each mailbox, each domain, each IP. We build it slowly, defend it per-mailbox, and pull a sender out of the shared pool weeks before any provider would.

+ +

- -
+ +
-
Authentication
-

SPF, DKIM, DMARC. Checked, not assumed.

+
-
+ +
+
+
Mailbox health
+ Per-mailbox state, rolling 7-day window +
+
+ live +
+
+ + +
+ {[ + { l: 'Healthy', c: 'bg-emerald-500', n: '3' }, + { l: 'Watch', c: 'bg-amber-500', n: '1' }, + { l: 'Throttled', c: 'bg-amber-600', n: '1' }, + { l: 'Quarantined', c: 'bg-rose-500', n: '1' }, + { l: 'Blocked', c: 'bg-rose-700', n: '1' }, + ].map((s) => ( +
+
+ + {s.l} +
+ {s.n} +
+ ))} +
+ + + + + +
+ {mailboxes.map((m) => { + const t = stateTone(m.state); + return ( +
+
+ +
+
{m.address}
+
spam {m.spam} · comp {m.complaint} · bounce {m.bounce}
+
+
+
+ + + {m.state} + +
+ + + + + + +
+ ); + })} +
+ + +
+ 7 mailboxes · 5 states + + + evaluated every event + +
+
+
+ app.warmbly.com/mailboxes +
+
+
+ + +
+
+
+
Stance
+

+ Why most warmup tools harm you. +

+

+ Deliverability is the surface that decides whether everything else in this product matters. We hold strong opinions on what is broken about the rest of the category, because every one of them maps to a real codepath we built differently. +

+
+ +
+ {critique.map((c, i) => ( +
+
+ {String(i + 1).padStart(2, '0')} +
{c.h}
+
+

{c.b}

+
+ ))} +
+
+
+ + +
+
+
+
Mailbox-first model
+

+ Every limit lives on a mailbox. +

+

+ The platform never has one global send target. A worker is a runtime, not a sender. A mailbox is the sender. Caps, gaps, pacing, warmup, suppression, and band membership all attach to the mailbox first. Workers spread mailboxes across machines, IPs, and Kafka topics so reputation never concentrates. +

+
+ +
+
+ mailbox model · v2 + mailbox is the unit of reputation, pacing, and policy +
+ +
+ + + + + + + + + + PER-MAILBOX POLICY + MAILBOX + EXECUTION SURFACE + + + {[ + { y: 50, label: 'cold cap', value: '50 / day' }, + { y: 110, label: 'min gap', value: '600s' }, + { y: 170, label: 'warmup vol', value: '10 to 40 / day' }, + { y: 230, label: 'pool spam score', value: 'block > 50' }, + { y: 290, label: 'band state', value: 'healthy to blocked' }, + ].map((p) => ( + + + {p.label} + {p.value} + + ))} + + + + + mbx + ben@acme.com + healthy + 47 / 50 sent · gap 11m + + + {[ + { y: 70, label: 'worker', value: 'w-01 · 192.0.2.14' }, + { y: 130, label: 'IP / PTR', value: 'smtp.warmbly.net' }, + { y: 190, label: 'auth', value: 'SPF · DKIM · DMARC' }, + { y: 250, label: 'suppression', value: 'workspace scope' }, + ].map((p) => ( + + + {p.label} + {p.value} + + ))} + + + {[72, 132, 192, 252, 312].map((y) => ( + + ))} + + + {[92, 152, 212, 272].map((y) => ( + + ))} + + + + + + +
+
+ +
+ {[ + { c: '#7dd3fc', t: 'Per-mailbox policy', d: 'Every cap, gap, pool, and band attaches here. No worker-wide knobs.' }, + { c: '#0ea5e9', t: 'The mailbox itself', d: 'One unit of reputation. One unit of pacing. One unit of band membership.' }, + { c: '#10b981', t: 'Execution surface', d: 'Worker, IP, PTR, auth, suppression. Replaceable, not load-bearing.' }, + ].map((l) => ( +
+ +
+
{l.t}
+
{l.d}
+
+
+ ))} +
+
+
+ + +
+
+
+
Defaults
+

+ The numbers we ship with. +

+

+ Tuned for the median sender on the median provider. Override per mailbox if you have a real reason. The right column is the reasoning we will defend in an incident review. +

+
+ +
+ {defaults.map((d, i) => ( +
+
+
+ {String(i + 1).padStart(2, '0')} · {d.k} +
+

{d.why}

+
+
+
{d.v}
+
+
+ ))} +
+ +
+ Source: internal/config/constants.go · internal/scheduler/email_scheduler.go · internal/repository/pg_warmup.go +
+
+
+ + +
+
+
+
Pool isolation
+

+ Four pools. Zero crossover. +

+

+ Mailboxes never silently move between pools. A trial mailbox stays in the free pool. A quarantined paid mailbox drops into recovery, not back to premium. Dedicated infrastructure runs alongside without sharing IPs. +

+
+ +
+ {pools.map((p) => ( +
+
+ {p.featured && ( +
+ Most paid plans +
+ )} +
+
+
{p.name}
+
{p.when}
+
+

{p.summary}

+
    + {p.rules.map((r) => ( +
  • + + {r} +
  • + ))} +
+
+
+ ))} +
+
+
+ + +
+
+
+
Auto-quarantine bands
+

+ Bands that act before providers do. +

+

+ Spam-folder placement on the X axis. Bands stack. Warmbly's thresholds sit well to the left of Google's and SES's published enforcement points, so we pull a mailbox out of the shared pool weeks before it would earn a public complaint. +

+
+ +
+
+
Spam placement · 0 to 100%
+
Per-mailbox rolling window
+
+ + +
+
+
+ HEALTHY · 0 to 10% +
+
+
+
+ WATCH · 10 to 20% +
+
+
+
+ QUARANTINED · 20 to 40% +
+
+
+
+ BLOCKED · 40% to 80% +
+
+
+
+ CATASTROPHIC +
+
+
+ + +
+ {gaugeTicks.map((t) => ( +
+
+
{t.label}
+
+ ))} +
+ + +
+ {bands.map((b) => { + const t = toneClass(b.tone); + return ( +
+
+ + {b.name} +
+
+
Spam{b.spam}
+
Complaint{b.complaint}
+
Bounce{b.bounce}
+
+

{b.action}

+
+ ); + })} +
+ +

+ Sample floors: spam-placement band requires at least 20 warmup deliveries in the past 7 days, complaint band requires at least 100 delivered messages in the past 30 days. External anchors: Google bulk sender keep spam below 0.1% and avoid reaching 0.3%, SES holds complaint below 0.1% and bounce below 5%. +

+
+
+
+ + +
+
+
+
Authentication coverage
+

+ SPF, DKIM, DMARC, PTR. Checked, not assumed. +

+

+ A mailbox cannot leave the connection wizard with broken authentication. We resolve every record live, surface what is missing in copy-pastable form, and re-verify on a daily cadence after launch. +

+
+ +
{auth.map((a) => (
-
DNS check
+
{a.tag}
{a.name}
-

{a.body}

+

{a.body}

+

+ on failure + {a.fail} +

))}
- -
+ +
-
Thresholds
-

We act weeks before providers do.

-

- Public thresholds vs. the bands we trigger on internally. -

- -
-
-
Provider
-
We act at
-
Warning
-
Provider acts
+
+
+
Suppression engine
+

+ Suppression is containment, not paperwork. +

+

+ The fastest response to a negative signal is to stop sending. Hard bounces, complaints, unsubscribes, and inbound STOP replies suppress the recipient before the next dispatch tick. Containment beats recovery every time. +

+
    +
  • Workspace-scoped suppression. Every campaign skips suppressed recipients on next tick.
  • +
  • RFC 8058 one-click unsubscribe. List-Unsubscribe-Post handled before the receipt page renders.
  • +
  • Inbound reply classifier auto-suppresses common opt-out phrasing.
  • +
- {bands.map((b) => ( -
-
{b.provider}
-
{b.us}
-
{b.warn}
-
{b.kill}
+ +
+
+
Trigger
+
Scope
+
Source
+
Behaviour
- ))} + {suppression.map((s) => ( +
+
{s.trigger}
+
+ {s.scope} +
+
{s.source}
+
{s.note}
+
+ ))} +
- -
+ +
-
Signals
-

What we watch, in real time.

+
+
+
Cold sending posture
+

+ Start small. Ramp boring.
Stay under the cap. +

+

+ These are operational heuristics, not protocol guarantees. They reflect what we observe on the platform and what every major receiver rewards. Treat the per-mailbox default cap as a ceiling, not a target. +

+
-
- {signals.map(([t, b]) => ( -
-
{t}
-

{b}

+
+ {posture.map((r, i) => ( +
+
{r.stage}
+
+ {r.range} +
+

{r.note}

+
+ ))} + +
+ 600s minimum gap, always + + Business hours by recipient TZ + + Warmup runs in parallel with cold +
+
+
+
+
+ + +
+
+
+
By the numbers
+

+ The shape of the safety policy, in one strip. +

+

+ Every number here is a real product default in the codebase, not a marketing rounding. +

+
+ +
+ {numbers.map((n) => ( +
+
+ {n.v} + {n.u} +
+
{n.l}
+
{n.src}
))}
- -
-
-
-
Go deeper
-

Full deliverability handbook.

+ +
+
+
+
Deliverability FAQ
+

+ Five questions we get most. +

+

+ More depth in the deliverability handbook. +

-
- {[ - { l: 'Email warmup explained', h: '/learn/email-warmup/' }, - { l: 'Deliverability handbook', h: '/learn/deliverability/' }, - { l: 'SPF, DKIM, DMARC', h: '/learn/spf-dkim-dmarc/' }, - { l: 'Inbox placement', h: '/learn/inbox-placement/' }, - { l: 'Cold email rules', h: '/learn/cold-email-rules/' }, - { l: 'Reputation recovery', h: '/learn/reputation-recovery/' }, - ].map((p) => ( - - {p.l} - - + +
+ {faq.map(([q, a]) => ( +
+ +
+
+

{a}

+
+
+
))}
+ +
diff --git a/site/src/pages/dpa.astro b/site/src/pages/dpa.astro index f28601ac..c5bba5e5 100644 --- a/site/src/pages/dpa.astro +++ b/site/src/pages/dpa.astro @@ -1,91 +1,590 @@ --- -import Legal from '../layouts/Legal.astro'; +import Layout from '../layouts/Layout.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; + +// ----------------------------------------------------------------- +// Last-updated date is the single source of truth for this DPA. +// ----------------------------------------------------------------- +const LAST_UPDATED = '2026-05-27'; + +// ----------------------------------------------------------------- +// Sidebar TOC. The id values must match the section ids in the body. +// ----------------------------------------------------------------- +const toc = [ + { num: '01', id: 'definitions', label: 'Definitions' }, + { num: '02', id: 'scope-roles', label: 'Scope and roles' }, + { num: '03', id: 'processing', label: 'Subject matter and duration' }, + { num: '04', id: 'data-subjects', label: 'Categories of data subjects' }, + { num: '05', id: 'data-categories', label: 'Categories of personal data' }, + { num: '06', id: 'instructions', label: 'Customer instructions' }, + { num: '07', id: 'confidentiality', label: 'Confidentiality and personnel' }, + { num: '08', id: 'security', label: 'Security measures' }, + { num: '09', id: 'subprocessors', label: 'Sub-processors' }, + { num: '10', id: 'transfers', label: 'International transfers' }, + { num: '11', id: 'rights', label: 'Data subject rights' }, + { num: '12', id: 'breach', label: 'Breach notification' }, + { num: '13', id: 'audits', label: 'Audits and inspections' }, + { num: '14', id: 'return-deletion', label: 'Return or deletion' }, + { num: '15', id: 'liability', label: 'Liability and limitations' }, + { num: '16', id: 'precedence', label: 'Order of precedence' }, + { num: '17', id: 'governing-law', label: 'Governing law' }, + { num: '18', id: 'annex-1', label: 'Annex 1: Processing details' }, + { num: '19', id: 'annex-2', label: 'Annex 2: Security measures' }, + { num: '20', id: 'annex-3', label: 'Annex 3: Sub-processors' }, +]; + +// ----------------------------------------------------------------- +// Definitions table. Plain English where the law allows, with the +// formal term kept first so legal teams can map back to GDPR. +// ----------------------------------------------------------------- +const definitions: Array<[string, string]> = [ + ['Controller', 'The party that decides why and how personal data is processed. Under this DPA, Customer is the Controller for the personal data Customer uploads, imports, or sends through the Warmbly platform.'], + ['Processor', 'The party that processes personal data on behalf of the Controller. Warmbly is the Processor for the personal data Customer entrusts to the platform.'], + ['Sub-processor', 'A third party engaged by Warmbly to process personal data on behalf of Customer, such as a hosting provider, an email-tracking processor, or a payments processor.'], + ['Personal Data', 'Any information relating to an identified or identifiable natural person that is processed under the Agreement.'], + ['Processing', 'Any operation performed on personal data, including collection, storage, transmission, analysis, deletion, and similar actions.'], + ['Data Subject', 'The natural person whose personal data is being processed.'], + ['Personal Data Breach', 'A confirmed security incident that leads to the accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to personal data processed under this DPA.'], + ['Data Protection Laws', 'All laws and regulations that apply to the parties’ processing of personal data under this DPA, including the EU GDPR, UK GDPR, the UK Data Protection Act 2018, the Swiss FADP, the California Consumer Privacy Act as amended, and any successor or equivalent statute.'], + ['Standard Contractual Clauses (SCCs)', 'The European Commission’s Standard Contractual Clauses for the transfer of personal data to third countries, in their then-current form approved by the European Commission.'], + ['UK IDTA', 'The United Kingdom International Data Transfer Agreement, together with the UK Addendum to the SCCs, as published by the Information Commissioner’s Office.'], +]; + +// ----------------------------------------------------------------- +// Annex 1: Processing details. Mirrors the structure mandated by the +// SCCs Annex I but kept readable. +// ----------------------------------------------------------------- +const annex1: Array<[string, string]> = [ + ['Subject matter', 'Processing of personal data in connection with the email warmup, cold outreach, deliverability monitoring, and related services that Warmbly makes available under the Agreement.'], + ['Duration', 'For the term of the Agreement and any wind-down period set out in the Privacy Policy or the Terms.'], + ['Nature', 'Hosting, storage, transmission, indexing, analytics, deliverability scoring, and routine maintenance of Customer Data through the Warmbly platform.'], + ['Purpose', 'Providing the platform to Customer, including sending and receiving email, syncing mailbox state, tracking opens and clicks, generating warmup activity, and surfacing analytics back to Customer.'], + ['Frequency', 'Continuous for the duration of the Agreement.'], + ['Data subjects', 'Customer’s authorised users, Customer’s contacts and recipients, and any other natural persons whose personal data Customer chooses to upload or process through the platform.'], + ['Categories', 'Identifiers (name, email address, IP address), professional contact information (company, role, phone), message content (subject lines, bodies, attachments), and behavioural signals (opens, clicks, replies, bounces, suppression status).'], + ['Special categories', 'None requested by Warmbly. Customer must not upload special category data, government identifiers, payment card data, or children’s data through the platform.'], + ['Recipients', 'Warmbly personnel acting under confidentiality obligations and the approved sub-processors listed at /subprocessors/.'], + ['Retention', 'For the term of the Agreement plus the retention windows set out in the Privacy Policy. Customer can delete records earlier from within the product.'], +]; + +// ----------------------------------------------------------------- +// Annex 2: Security measures. Short, factual, mapped to what is +// actually implemented in this repo. +// ----------------------------------------------------------------- +const annex2 = [ + { + cat: 'Encryption', + items: [ + 'Envelope encryption rooted in AWS KMS. A unique 32-byte Data Encryption Key (DEK) is generated per customer and never stored in plaintext at rest.', + 'Sensitive payloads (mailbox credentials, message bodies, OAuth tokens) are sealed with AES-256-GCM using the customer’s DEK.', + 'Decrypted DEKs are cached in Redis with a strict TTL so the plaintext key is not held in memory longer than required.', + 'TLS 1.2 or higher in transit for all customer-facing endpoints and inter-service traffic.', + ], + }, + { + cat: 'Access control', + items: [ + 'Role-based access control across the application surface, with workspace, organisation, and admin scopes.', + 'Single sign-on and short-lived credentials for production access. No shared accounts.', + 'Least-privilege IAM for backend, consumer, worker, tracking, and realtime services.', + 'Audit logs of administrative actions, ban events, rate-limit overrides, and key rotations.', + ], + }, + { + cat: 'Isolation and integrity', + items: [ + 'Workers run as stateless executors. They do not hold direct SQL access to the control-plane database.', + 'Tenant isolation enforced at the application layer, with workspace identifiers carried on every query and event.', + 'Idempotency keys on deliverability events and webhook handlers protect against replay and duplicate processing.', + ], + }, + { + cat: 'Operational security', + items: [ + 'Rolling encrypted backups of the control-plane database. Restore tested on a documented cadence.', + 'Vulnerability monitoring across application dependencies, container base images, and infrastructure.', + 'Documented incident response process, with breach notification triggers and on-call rotation.', + 'Personnel security training, background checks where lawful, and signed confidentiality obligations.', + ], + }, + { + cat: 'Anti-abuse controls', + items: [ + 'CAPTCHA on authentication-sensitive flows, including login, registration, password reset, and confirmation.', + 'Per-user API rate limiting and websocket rate limiting backed by Redis.', + 'Warmup token verification with spam-score tracking and auto-blocking of mailboxes from shared pools.', + 'Mailbox-sync abuse detection on workers, with burst and hourly thresholds.', + ], + }, +]; --- - -

- This Data Processing Addendum ("DPA") supplements the Warmbly Terms of Service and governs the processing of personal data by Warmbly Labs, Inc. ("Processor") on behalf of the customer ("Controller") in the course of providing the Warmbly services. -

+ +
+ -

1. Definitions

-

- "Personal Data", "Processor", "Controller", "Sub-processor", "Data Subject", "Personal Data Breach" and "Processing" have the meanings given in the EU General Data Protection Regulation (Regulation 2016/679) ("GDPR"). -

+
+
+
+ Legal + Data processing +
-

2. Scope and roles

-

- Where Warmbly processes Personal Data on behalf of the Controller in providing the services, Warmbly acts as a Processor and the Controller acts as the Controller. Each party will comply with applicable data protection laws. -

+

+ Data processing
addendum. +

-

3. Processing details

-
    -
  • Subject matter: performance of the Warmbly services described in the Terms.
  • -
  • Duration: the duration of the customer agreement plus any retention period set out in the Privacy Policy.
  • -
  • Nature and purpose: hosting, storing, transmitting and processing customer content (contacts, sequences, sent and received mail metadata, replies, suppressions) to deliver the service.
  • -
  • Types of Personal Data: account data (customer staff), recipient data (contacts), mailbox content metadata, IP addresses, behavioural events.
  • -
  • Categories of Data Subjects: Controller's team members; Controller's contacts and recipients.
  • -
+

+ This addendum applies between Warmbly, acting as Processor, and the Customer, acting as Controller. It is incorporated by reference into the Warmbly Terms of Service and governs how Warmbly processes personal data on Customer’s behalf. +

-

4. Controller instructions

-

- Warmbly processes Personal Data only on documented Controller instructions, including those given through the platform itself, unless required to do otherwise by applicable law. -

+
+
+ Last updated + {LAST_UPDATED} +
+ + + Download as PDF + + + Read the Terms + +
+
+
+
-

5. Confidentiality

-

- Warmbly ensures personnel authorised to process Personal Data are subject to confidentiality obligations. -

+ +
+
-

6. Security

-

- Warmbly implements appropriate technical and organisational measures, including envelope encryption with AWS KMS, AES-256-GCM at rest, TLS in transit, role-based access control, audit logging, and the security controls described at /security. -

+ + -

8. International transfers

-

- Where Personal Data is transferred from the EEA, UK or Switzerland to a third country, Warmbly relies on the appropriate transfer mechanism (Standard Contractual Clauses, UK IDTA, or equivalent), which is hereby incorporated by reference. -

+ +
-

9. Data subject requests

-

- Taking into account the nature of the processing, Warmbly will assist the Controller in responding to data subject requests through technical and organisational measures. -

+ +
+

+ This Data Processing Addendum (the "DPA") supplements the Warmbly Terms of Service (the "Agreement") between Warmbly and the Customer. It governs the processing of Personal Data by Warmbly as Processor on behalf of the Customer as Controller in connection with the Warmbly platform. This DPA takes effect on the date the Customer accepts the Agreement, or, if later, on the date this DPA is countersigned. +

+

+ Capitalised terms not defined here have the meanings given in the Agreement or, where applicable, in the Data Protection Laws. To the extent there is a conflict with the Agreement on a data protection matter, this DPA controls. +

+
-

10. Personal Data breaches

-

- Warmbly will notify the Controller without undue delay after becoming aware of a Personal Data Breach affecting the Controller's data. Notification will include the nature of the breach, the categories and approximate number of Data Subjects and records affected, likely consequences and the measures taken or proposed. -

+ +
+
+ 01 +

Definitions

+
+

+ The following terms have the meanings set out below. Where a term is also defined in applicable Data Protection Laws, the statutory meaning prevails to the extent required. +

+
+ {definitions.map(([term, body]) => ( +
+
{term}
+
{body}
+
+ ))} +
+
-

11. Audits

-

- Warmbly will make available information necessary to demonstrate compliance with this DPA. Controllers may request, no more than once per twelve-month period, a copy of the most recent third-party audit or attestation (e.g. SOC 2 Type II once available). On-site audits may be conducted by mutual agreement subject to reasonable confidentiality and security requirements. -

+ +
+
+ 02 +

Scope and roles

+
+

+ This DPA applies wherever Warmbly processes Personal Data on Customer’s behalf in the course of providing the platform. In that processing, Customer is the Controller and Warmbly is the Processor. Each party will comply with the Data Protection Laws that apply to it in its respective role. +

+

+ Customer is responsible for the lawfulness of the Personal Data it uploads, imports, or sends through the platform, for having a valid legal basis for processing, and for providing any notices and obtaining any consents required from Data Subjects. Warmbly is responsible for processing that Personal Data only as described in this DPA and on Customer’s instructions. +

+

+ Where Warmbly determines the means and purposes of processing, for example for billing, fraud prevention, abuse detection, security telemetry, or aggregated product analytics, Warmbly acts as an independent Controller for that processing and the Warmbly Privacy Policy applies. +

+
-

12. Return and deletion

-

- On termination of the customer agreement, Warmbly will, at the Controller's choice, delete or return the Personal Data, unless retention is required by law. -

+ +
+
+ 03 +

Subject matter, nature, purpose, and duration

+
+

+ The subject matter, nature, and purpose of the processing are the operation and provision of the Warmbly platform to Customer, as described in the Agreement and in Annex 1. +

+

+ The duration of processing is the term of the Agreement plus the wind-down and retention periods described in the Privacy Policy and in this DPA. Customer can shorten that duration at any time by deleting records from within the product or by terminating the Agreement. +

+
-

13. Liability

-

- Each party's liability under this DPA is subject to the limitations and exclusions of liability set out in the Terms. -

+ +
+
+ 04 +

Categories of data subjects

+
+

+ The Personal Data processed under this DPA concerns the following categories of Data Subjects: +

+
    +
  • Customer’s authorised users and team members who access the platform.
  • +
  • Customer’s contacts, leads, and recipients to whom Customer sends mail through the platform.
  • +
  • Senders and recipients of replies received in Customer’s connected mailboxes.
  • +
  • Other natural persons whose Personal Data Customer chooses to upload, import, or otherwise process through the platform.
  • +
+
-

14. Order of precedence

-

- In the event of any conflict between this DPA and the Terms, the DPA controls with respect to data protection matters. -

+ +
+
+ 05 +

Categories of personal data

+
+

+ The categories of Personal Data processed include: +

+
    +
  • Identifiers. Names, email addresses, IP addresses, user-agent strings.
  • +
  • Professional contact information. Company name, job title, phone number, social profile links, and similar business contact attributes Customer chooses to import.
  • +
  • Message content. Subject lines, message bodies, attachments, signatures, and metadata of mail sent or received through Customer’s connected mailboxes.
  • +
  • Behavioural signals. Open events, click events, replies, bounces, unsubscribes, suppression status, and warmup activity.
  • +
  • Authentication and configuration data. OAuth tokens, SMTP credentials, send-rate settings, and mailbox health state, stored using envelope encryption.
  • +
+

+ Customer must not upload special categories of Personal Data, government-issued identifiers, payment card data, or data relating to children to the platform. The platform is not designed for that data, and Customer remains responsible if it does so. +

+
-

15. Signed copies

-

- A signed counter-signed copy of this DPA is available on request at legal@warmbly.com. -

- + +
+
+ 06 +

Customer instructions

+
+

+ Warmbly processes Personal Data only on documented instructions from Customer, including the instructions Customer issues by configuring and using the platform itself, unless required to process by applicable law. If Warmbly is required by law to process Personal Data outside Customer’s instructions, Warmbly will inform Customer of that requirement before processing, unless the law prohibits that notice on important grounds of public interest. +

+

+ Warmbly will notify Customer if, in its opinion, an instruction infringes the Data Protection Laws. Warmbly may then suspend the affected processing until Customer modifies or confirms the instruction in writing. +

+
+ + +
+
+ 07 +

Confidentiality and personnel

+
+

+ Warmbly limits access to Personal Data to personnel who need it to perform the Agreement. Those personnel are bound by written confidentiality obligations or are under an appropriate statutory obligation of confidentiality. Warmbly trains its personnel on data protection and security responsibilities and applies role-based access control to production systems. +

+
+ + +
+
+ 08 +

Security measures

+
+

+ Warmbly implements and maintains appropriate technical and organisational measures designed to protect Personal Data against accidental or unlawful destruction, loss, alteration, unauthorised disclosure, or access. Those measures are summarised in Annex 2 and described in more detail at trust and security. +

+

+ In summary, sensitive Personal Data is protected by envelope encryption. AWS KMS acts as the root of trust. Each customer is issued a per-customer Data Encryption Key (DEK). DEKs are never stored in plaintext at rest. Sensitive payloads are sealed with AES-256-GCM using the DEK, and the encrypted blobs are stored in dedicated infrastructure. Plaintext DEKs are cached in Redis with a strict TTL so they are not held in memory longer than required. All inter-service and customer-facing traffic uses TLS 1.2 or higher. Access is governed by role-based access control with audit logging of administrative actions. +

+

+ Warmbly reviews and updates its security measures over time. Warmbly may replace a security measure with one that provides an equivalent or higher level of protection, but will not materially reduce the overall protection of Personal Data without Customer’s consent. +

+
+ + +
+
+ 09 +

Sub-processors

+
+

+ Customer provides general written authorisation for Warmbly to engage Sub-processors to process Personal Data on Customer’s behalf for the purposes described in the Agreement. The current list of approved Sub-processors is published at /subprocessors. +

+

+ Warmbly will give Customer at least 30 days’ prior notice of any addition or replacement of a Sub-processor by updating the Sub-processors page and by providing an in-product notice. Customer may object to a new Sub-processor on reasonable data-protection grounds by notifying Warmbly during that 30-day period. If the parties are unable in good faith to reach a workable solution, Customer may terminate the affected services on written notice and receive a pro-rata refund of pre-paid fees for the unused term. +

+

+ Warmbly enters into written agreements with each Sub-processor that impose data protection obligations no less protective than those in this DPA. Warmbly remains liable to Customer for the performance of each Sub-processor’s obligations to the extent required by the Data Protection Laws. +

+
+ + +
+
+ 10 +

International data transfers

+
+

+ Where Personal Data subject to the EU GDPR is transferred from the European Economic Area to a country that has not been recognised by the European Commission as providing an adequate level of protection, the parties agree that such transfer is governed by the Standard Contractual Clauses (Module Two: Controller to Processor), which are hereby incorporated into this DPA by reference. The annexes to the SCCs are populated using the information in Annex 1, Annex 2, and Annex 3 of this DPA. +

+

+ Where Personal Data subject to the UK GDPR is transferred from the United Kingdom to a country that has not been recognised as providing an adequate level of protection, the parties agree that such transfer is governed by the UK International Data Transfer Agreement, together with the UK Addendum to the SCCs, which are hereby incorporated into this DPA by reference. +

+

+ Where Personal Data subject to the Swiss Federal Act on Data Protection is transferred outside Switzerland to a country that has not been recognised as providing adequate protection, the SCCs apply with the adjustments published by the Swiss Federal Data Protection and Information Commissioner, including treating references to EU member-state law and supervisory authorities as references to Swiss law and the Swiss authority where relevant. +

+

+ Warmbly assesses, on an ongoing basis, the laws and practices of countries to which Personal Data is transferred, and implements supplementary technical, organisational, and contractual measures where appropriate. Customer can request a summary of the current transfer impact assessment from legal@warmbly.com. +

+
+ + +
+
+ 11 +

Data subject rights assistance

+
+

+ Taking into account the nature of the processing and the information available, Warmbly will assist Customer by appropriate technical and organisational measures to respond to requests by Data Subjects to exercise their rights under the Data Protection Laws, including rights of access, rectification, erasure, restriction, portability, and objection. +

+

+ The platform provides in-product tooling so Customer can read, edit, export, and delete Personal Data directly. Where a request cannot be satisfied through that tooling, Customer can contact legal@warmbly.com for assistance. If a Data Subject contacts Warmbly directly, Warmbly will inform the Data Subject that the request should be directed to Customer, and will notify Customer without undue delay. +

+
+ + +
+
+ 12 +

Personal data breach notification

+
+

+ Warmbly will notify Customer without undue delay, and in any event within 72 hours of becoming aware, of a confirmed Personal Data Breach affecting Customer’s Personal Data. Notification will be made to the security or admin contact on file for the workspace, and in parallel by email to the billing contact where appropriate. +

+

+ The notification will, to the extent then known, include: +

+
    +
  • The nature of the breach, including the categories and approximate number of Data Subjects and records concerned.
  • +
  • The likely consequences of the breach.
  • +
  • The measures Warmbly has taken or proposes to take to address the breach, including measures to mitigate its possible adverse effects.
  • +
  • The name and contact details of a point of contact for further information.
  • +
+

+ Warmbly will provide updates as additional information becomes available. Warmbly’s notification or response to a Personal Data Breach is not an acknowledgement of fault or liability. +

+
+ + +
+
+ 13 +

Audits and inspections

+
+

+ Warmbly will make available to Customer information reasonably necessary to demonstrate compliance with this DPA. On reasonable prior written notice, and no more than once in any twelve-month period, Customer may request a copy of the most recent third-party audit report, attestation, or summary held by Warmbly that relates to the platform. +

+

+ Where a Data Protection Law or supervisory authority requires an on-site audit, the parties will agree in good faith on the scope, timing, and conditions of the audit, taking into account the need to safeguard the confidentiality and security of other customers and the integrity of the platform. Audits will be conducted during normal business hours, with reasonable steps to avoid disruption, by an auditor agreed between the parties, and under written confidentiality obligations. Customer is responsible for the costs of any audit it initiates, unless the audit reveals a material breach of this DPA by Warmbly. +

+
+ + +
+
+ 14 +

Return or deletion at end of services

+
+

+ On termination or expiry of the Agreement, at Customer’s choice, Warmbly will return or delete the Personal Data processed under this DPA, unless the Data Protection Laws or another applicable law requires further storage. Customer can export Personal Data directly from the platform at any time during the term. +

+

+ Warmbly will complete deletion within 30 days of termination, except for: backup copies, which are deleted on the rolling backup schedule documented in the Privacy Policy; records that Warmbly is legally required to retain, which are isolated and protected against further processing; and aggregated, deidentified data that does not identify Customer or any Data Subject. +

+
+ + +
+
+ 15 +

Liability and limitations

+
+

+ Each party’s aggregate liability arising out of or related to this DPA, whether in contract, tort, or under any other theory of liability, is subject to the limitations and exclusions of liability set out in the Agreement. Any reference in the Agreement to the liability of a party means the aggregate liability of that party under the Agreement and this DPA together. +

+

+ Nothing in this DPA limits or excludes liability that cannot be limited or excluded under applicable law, including liability for Warmbly’s wilful misconduct or fraud, or, where applicable, the rights of Data Subjects under the SCCs. +

+
+ + +
+
+ 16 +

Order of precedence and severability

+
+

+ In the event of any conflict between this DPA and the Agreement on a data protection matter, this DPA controls. Where the SCCs or the UK IDTA apply to a transfer, those instruments prevail over any conflicting terms of the Agreement or this DPA, to the extent required. +

+

+ If any provision of this DPA is held to be invalid or unenforceable, the remaining provisions will continue in full force and effect, and the parties will replace the invalid or unenforceable provision with a valid and enforceable provision that achieves, to the greatest extent possible, the same commercial and legal effect. +

+
+ + +
+
+ 17 +

Governing law

+
+

+ This DPA is governed by the law and subject to the jurisdiction specified in the Agreement, except that the SCCs, where they apply, are governed by the law of the EU member state designated in the SCCs themselves, and the UK IDTA, where it applies, is governed by the laws of England and Wales as required by that instrument. Where the Agreement does not specify a governing law, the parties will agree a governing law in good faith, taking into account the location of the Controller and the practical requirements of the Data Protection Laws. +

+
+ + +
+
+ 18 +

Annex 1 · Processing details

+
+

+ This Annex describes the processing carried out by Warmbly on behalf of Customer, in the format expected by the SCCs Annex I.B and equivalent UK and Swiss instruments. +

+
+ + + + + + + + + {annex1.map(([field, body]) => ( + + + + + ))} + +
FieldDescription
{field}{body}
+
+
+ + +
+
+ 19 +

Annex 2 · Security measures

+
+

+ The technical and organisational measures Warmbly applies to the processing of Personal Data are summarised below. The current detailed control set is published at trust and security. +

+
+ {annex2.map((group) => ( +
+
+ +
{group.cat}
+
+
    + {group.items.map((item) => ( +
  • {item}
  • + ))} +
+
+ ))} +
+
+ + +
+
+ 20 +

Annex 3 · Approved sub-processors

+
+

+ The current list of approved Sub-processors, including the processing they perform, the regions in which they operate, and the countries in which they store Personal Data, is published and maintained at /subprocessors. That page is incorporated into this DPA by reference and forms part of Annex 3. +

+
+
Subscribe to changes
+

+ Email legal@warmbly.com to receive notice of Sub-processor changes by email. Warmbly will give at least 30 days’ notice before any change that affects the processing of Customer Personal Data. +

+
+
+ + +
+
+
Signatures
+

+ This DPA is binding on the parties as soon as Customer accepts the Agreement. A counter-signed copy on Warmbly letterhead is available on request. Send any signature, audit, or transfer-mapping requests to legal@warmbly.com. +

+
+ Version 2026.05.27 + Last updated {LAST_UPDATED} + Contact legal@warmbly.com +
+
+
+ +
+
+
+ diff --git a/site/src/pages/privacy.astro b/site/src/pages/privacy.astro index 9d993599..b3faaa86 100644 --- a/site/src/pages/privacy.astro +++ b/site/src/pages/privacy.astro @@ -1,107 +1,473 @@ --- -import Legal from '../layouts/Legal.astro'; +import Layout from '../layouts/Layout.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; + +const lastUpdated = '2026-05-27'; + +const sections = [ + { id: 'who-we-are', n: '01', label: 'Who we are' }, + { id: 'data-we-collect', n: '02', label: 'Data we collect' }, + { id: 'how-we-use-data', n: '03', label: 'How we use your data' }, + { id: 'legal-bases', n: '04', label: 'Legal bases' }, + { id: 'encryption-storage', n: '05', label: 'Encryption and storage' }, + { id: 'subprocessors', n: '06', label: 'Subprocessors' }, + { id: 'retention', n: '07', label: 'Data retention' }, + { id: 'your-rights', n: '08', label: 'Your rights' }, + { id: 'international', n: '09', label: 'International transfers' }, + { id: 'cookies', n: '10', label: 'Cookies' }, + { id: 'children', n: '11', label: "Children's data" }, + { id: 'changes', n: '12', label: 'Changes to this policy' }, + { id: 'contact', n: '13', label: 'Contact' }, +]; + +const docs = [ + { label: 'Terms of Service', href: '/terms/' }, + { label: 'Privacy Policy', href: '/privacy/' }, + { label: 'Data Processing Addendum', href: '/dpa/' }, + { label: 'Acceptable Use', href: '/acceptable-use/' }, + { label: 'Subprocessors', href: '/subprocessors/' }, + { label: 'Cookies', href: '/cookies/' }, +]; + +const path = Astro.url.pathname; --- - -

- This Privacy Policy describes how Warmbly Labs, Inc. ("Warmbly", "we", "us") collects, uses and shares personal information when you visit our websites, create an account, or use our services. -

+ +
+ -

1. The data we collect

-

Account data

-
    -
  • Name, email address, password hash, profile photo (if uploaded).
  • -
  • Workspace name, billing address, payment method (handled by Stripe).
  • -
  • Team-member emails and roles you add to the workspace.
  • -
+
+
+ + Legal + + Privacy Policy +
-

Mailbox connection data

-
    -
  • Mailbox provider, mailbox address, OAuth tokens or SMTP credentials.
  • -
  • Display name, signature, send rate configuration.
  • -
  • Encrypted credentials are stored using envelope encryption with AWS KMS.
  • -
+

+ Privacy. +

-

Customer content

-
    -
  • Contacts, sequences, message templates, sent and received message metadata, replies, suppression entries.
  • -
  • Files you upload (e.g. CSV imports).
  • -
+

+ What we collect, why we need it, where it lives, and what you can ask us to do with it. Plain English, no dark patterns. +

-

Telemetry

-
    -
  • Server-side logs (IP, user agent, timestamps, request paths) used for security, billing, and abuse prevention.
  • -
  • Product analytics for feature usage and stability (workspace owner can disable for the workspace).
  • -
  • Error reports (Sentry) — scrubbed of customer content.
  • -
+
+
+ + Last updated {lastUpdated} +
+ + Jump to data we collect + + +
+
+
-

2. How we use it

-
    -
  • To provide the service: connect mailboxes, send mail, surface replies, run analytics you can read.
  • -
  • To bill you accurately.
  • -
  • To detect and prevent abuse, fraud, and platform misuse.
  • -
  • To send transactional emails (account activity, security alerts, billing receipts).
  • -
  • To improve the product based on aggregated usage patterns.
  • -
  • To respond to support requests.
  • -
-

- We do not sell personal information to advertisers. -

+ + -

3. Legal bases (GDPR)

-
    -
  • Performance of a contract — almost all account and service use.
  • -
  • Legitimate interest — security, fraud prevention, product improvement.
  • -
  • Consent — optional product analytics, optional marketing email subscriptions.
  • -
  • Compliance with legal obligations — invoicing, tax records.
  • -
+ +
+
-

4. Sharing

-

- We share data with vetted subprocessors as needed to deliver the service. The complete list is published at /subprocessors. We do not share customer content with third parties for their own marketing. -

-

- We may disclose data if required by law, court order, or to protect our rights or the safety of users. -

+ + -

6. Retention

-
    -
  • Account data: while your account is active, plus a wind-down period of up to 30 days after closure.
  • -
  • Customer content: while your account is active. On closure, we delete or anonymise within 30 days unless retention is required by law.
  • -
  • Backups: rolling encrypted backups retained for up to 35 days.
  • -
  • Telemetry: aggregated indefinitely; raw logs retained 30 days unless flagged for incident investigation.
  • -
+ +
+ +
+

+ Warmbly is an open-source email warmup and cold outreach platform. Running the service means we handle account information, mailbox credentials, message bodies, and recipient lists on your behalf. This page explains how that works, where the data goes, and what you can ask us to do with it. +

+

+ We use simple language on purpose. If anything here is unclear, write to privacy@warmbly.com and we will rewrite it. +

+
-

7. Your rights

-

- Depending on your jurisdiction, you may have the right to access, correct, delete, restrict or export your personal information, and to object to certain processing. Email privacy@warmbly.com and we will respond within 30 days. -

+ +
+
+ 01 +

Who we are

+
+

+ Warmbly is the operator of the Warmbly email warmup and cold outreach platform. When you use the service we are the controller of the personal data we collect from you about your account, and we are the processor of the data you bring into the platform about your contacts and recipients. +

+

+ Privacy questions, data subject requests, and security reports all go to privacy@warmbly.com. We try to acknowledge within two business days. +

+
-

8. Security

-

- We protect customer data with envelope encryption (AES-256-GCM with KMS-derived data keys), TLS in transit, strict access controls, audit logging, and ongoing security review. Details at /security. -

+ +
+
+ 02 +

Data we collect

+
+

+ We collect the smallest set of information we need to run the product, bill it, and keep it safe to use. Roughly four buckets. +

-

9. Children

-

- The service is not intended for children under 16. We do not knowingly collect data from children. If you believe a child has provided us data, contact us and we will delete it. -

+
+
+
+

Account data

+ Controller +
+

+ Your email address, name, workspace name, password hash, role, and team membership. If you pay us, Stripe handles the card details and gives us back a customer identifier, the country we should charge tax in, and the last four digits for receipts. +

+
-

10. Changes

-

- We may update this policy. Material changes will be announced via email or in-app notice at least 30 days in advance. -

+
+
+

Mailbox data

+ Processor +
+

+ The mailboxes you connect, and the credentials needed to send and receive on your behalf. For Google and Microsoft, that is an OAuth refresh token scoped to the permissions you grant during sign-in. For generic providers, it is the IMAP and SMTP host, port, and username, plus an encrypted copy of the password. Credentials are sealed with envelope encryption before they touch persistent storage. +

+
-

11. Contact

-

- Warmbly Labs, Inc. — 1209 N. Orange Street, Wilmington, DE 19801, USA. Privacy contact: privacy@warmbly.com. -

- +
+
+

Message data

+ Processor +
+

+ The campaigns you send, the warmup mail the platform generates on your behalf, replies and bounces synced back from the mailbox, attachments where you choose to use them, and the tracking events we record when a recipient opens an email or clicks a link. Message bodies are stored encrypted where the design calls for it, and message content is not used to train models. +

+
+ +
+
+

Usage data

+ Controller +
+

+ Server logs, request paths, IP addresses, user agent strings, error reports, and session events. We need this to operate the service, debug problems, rate limit abusive clients, and investigate security incidents. We do not use it to build advertising profiles. +

+
+
+
+ + +
+
+ 03 +

How we use your data

+
+

+ We use the data above to do the things you actually signed up for, and a small number of things you would reasonably expect a sending platform to do. +

+ +
    + {[ + ['Operate the service', 'Send the campaigns and warmup mail you scheduled, sync replies, classify them, and surface the results in your inbox and dashboards.'], + ['Prevent abuse', 'Detect spammy patterns, throttle bad actors, enforce per-mailbox rate limits, and protect shared warmup pools from accounts that damage everyone else\'s reputation.'], + ['Deliverability analytics', 'Aggregate signals like bounces, complaints, opens, and reply sentiment so you can see how a mailbox is doing and so we can spot platform-wide issues early.'], + ['Billing', 'Track plan, seats, and connected mailboxes, generate invoices through Stripe, and apply any tax we are required to collect.'], + ['Support', 'Respond to your messages, debug your account when you ask us to, and follow up on incidents.'], + ['Compliance', 'Keep the records the law requires us to keep, respond to lawful requests, and document our security and privacy controls.'], + ].map(([k, v]) => ( +
  • + +
    + {k}. + {v} +
    +
  • + ))} +
+ +

+ We do not sell personal information. We do not rent contact lists. We do not share message content with advertisers. +

+
+ + + + + +
+
+ 05 +

Encryption and storage

+
+

+ Sensitive material is encrypted with an envelope scheme. The root of trust lives in a managed key service, application data is sealed per user with a unique key, and the control plane only ever sees ciphertext for those fields. +

+ +
+
+

Root of trust

+

AWS KMS holds the customer master key. Plaintext data keys are never written to disk and never leave the encryption boundary in clear form.

+
+
+

Per-user data key

+

Every user gets a 32 byte data encryption key (DEK) generated by KMS. Application-level fields such as mailbox credentials are sealed with AES-256-GCM using that key.

+
+
+

Key ciphertext

+

The encrypted (wrapped) DEK is stored in DynamoDB. The plaintext DEK is only obtained by calling KMS to unwrap it, and only when an operation actually needs to decrypt a payload.

+
+
+

Hot key cache

+

To avoid hitting KMS on every send, unwrapped DEKs are held in Redis with a short time-to-live. They expire automatically and can be evicted on demand if a key needs to be rotated.

+
+
+

Control plane

+

PostgreSQL holds operational state such as your account, your mailboxes, campaign configuration, and aggregate analytics. Fields marked as encrypted are stored only as ciphertext. Larger message payloads are encrypted where the design calls for it before they are persisted.

+
+
+ +

+ Encryption boundaries are documented in the codebase under internal/app/cipher and internal/infrastructure/kms. Warmbly is open source, so the implementation can be inspected. +

+
+ + +
+
+ 06 +

Subprocessors

+
+

+ We use a small set of vetted vendors to run the service: cloud infrastructure, the payment processor, transactional email for account notices, and error reporting. The current list, what each one does, and where it operates is published at /subprocessors. +

+

+ If you need notice of changes, customers on paid plans are notified by email when a new subprocessor is added with reasonable lead time before it begins processing data. +

+
+ + +
+
+ 07 +

Data retention

+
+

+ We keep data for as long as you have an account, and then only as long as we need to wind things down cleanly or as the law requires. +

+ +
+
+
While the account is active
+

Account data, mailbox credentials, message data, and analytics live in the platform so the product can do its job.

+
+
+
After account deletion
+

Account data and message content are deleted within 30 days. Encrypted backups roll off on their own schedule (up to 35 days). Connected mailbox tokens are revoked and the credentials are erased.

+
+
+
Billing records
+

Invoices and tax records are kept for the period required by applicable tax and accounting law, typically seven years.

+
+
+
Suppression lists
+

If a recipient has unsubscribed, bounced hard, or complained, we keep a minimal record (a hashed address and the suppression reason) so the platform does not send to them again. This is required to honor the unsubscribe.

+
+
+
+ + +
+
+ 08 +

Your rights

+
+

+ Depending on where you live, you may have some or all of the rights below over your personal data. We honor them regardless of jurisdiction whenever the request is reasonable to fulfill. +

+ +
    + {[ + ['Access', 'Get a copy of the personal data we hold about you.'], + ['Rectification', 'Ask us to correct anything that is inaccurate or out of date.'], + ['Deletion', 'Ask us to erase your personal data. We will, unless we are required to keep it.'], + ['Portability', 'Receive your data in a structured, machine-readable format.'], + ['Restriction', 'Ask us to pause processing while a dispute or correction is resolved.'], + ['Objection', 'Object to processing that is based on legitimate interest.'], + ['Withdraw consent', 'Withdraw consent for anything you opted into (such as product analytics) at any time.'], + ['Complaint', 'Lodge a complaint with the supervisory authority in your country.'], + ].map(([k, v]) => ( +
  • +
    {k}
    +

    {v}

    +
  • + ))} +
+ +

+ To exercise any of these, email privacy@warmbly.com. We will verify the request comes from you and respond within 30 days. If we cannot complete the request we will tell you why. +

+
+ + +
+
+ 09 +

International transfers

+
+

+ Warmbly is operated from data centers in the United States and the European Union. Depending on your plan and region, your data may be stored in either region. Some subprocessors operate globally, which can mean data is transmitted across borders. +

+

+ For transfers out of the European Economic Area, the United Kingdom, or Switzerland to a country without an adequacy decision, we rely on the European Commission Standard Contractual Clauses, the UK International Data Transfer Addendum, or an equivalent mechanism. These commitments are incorporated by reference into our Data Processing Addendum. +

+
+ + +
+
+ 10 +

Cookies

+
+

+ Warmbly uses a small number of first-party cookies for things like keeping you logged in, remembering your workspace, and protecting forms from cross-site request forgery. We do not run advertising trackers. +

+

+ The full list, including third-party cookies set by Stripe and the CAPTCHA provider on relevant pages, is at /cookies. +

+
+ + +
+
+ 11 +

Children's data

+
+

+ Warmbly is a business tool. It is not intended for use by anyone under 16, or under 18 in jurisdictions where that is the threshold for digital services. We do not knowingly collect personal data from children. If you believe a child has signed up, contact us and we will delete the account. +

+
+ + +
+
+ 12 +

Changes to this policy

+
+

+ We update this policy when the product changes, when the law changes, or when we find a clearer way to say something. Material changes are announced by email or in-app notice at least 30 days before they take effect, and the "Last updated" date at the top of this page always reflects the most recent revision. +

+
+ + +
+
+ 13 +

Contact

+
+

+ Privacy questions, data subject requests, breach reports, and supervisory authority correspondence all go to privacy@warmbly.com. For general legal matters, write to legal@warmbly.com. +

+
+ + +
+
Last updated {lastUpdated}
+ +
+
+
+
+ + + diff --git a/site/src/pages/subprocessors.astro b/site/src/pages/subprocessors.astro index be5e2696..5f2af2ca 100644 --- a/site/src/pages/subprocessors.astro +++ b/site/src/pages/subprocessors.astro @@ -1,50 +1,475 @@ --- -import Legal from '../layouts/Legal.astro'; +import Layout from '../layouts/Layout.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; +import Icon from '../components/Icon.astro'; +import CTA from '../components/CTA.astro'; -const rows = [ - ['AWS', 'Compute, storage, KMS, DynamoDB, S3, Kafka (MSK), Postgres (RDS)', 'us-east-1, eu-west-1', 'United States, Ireland'], - ['Cloudflare', 'CDN, edge security, Turnstile CAPTCHA, DDoS protection', 'Global', 'Global'], - ['Stripe', 'Payments, billing, tax', 'us-east-1', 'United States'], - ['PostHog', 'Product analytics (workspace-owner-disable-able)', 'eu-central-1', 'Germany'], - ['Slack', 'Customer support escalations', 'us-east-1', 'United States'], - ['Linear', 'Internal issue tracking referenced in support replies', 'us-east-1', 'United States'], - ['Sentry', 'Error reporting, scrubbed of customer content', 'us-east-1', 'United States'], - ['Postmark', 'Transactional account email (receipts, security alerts)', 'us-east-1', 'United States'], - ['HubSpot', 'Marketing-website forms (sales contact only)', 'us-east-1', 'United States'], +/* ----------------------------------------------------------------- + Subprocessors page. + + Layout shape: + 1. Hero (HeroAtmosphere) with eyebrow, title, last-updated badge, + and short copy about notifying customers of material changes. + 2. Subscribe-to-changes strip with a mailto. + 3. Designer-grade subprocessor table inside container-page. + Desktop: full table with hover state. + Mobile: same data collapses into stacked cards. + 4. "How we evaluate subprocessors" short list. + 5. "What changes when we add a subprocessor" notice + window. + 6. "Previous subprocessors" placeholder / empty state. + 7. Closing CTA. + + Tone rules: + - No em dashes. + - No fake compliance badges or "certified" claims for vendors. + - Vendors are pulled from Warmbly's real architecture per CLAUDE.md. + + Astro JSX parser caveat: + `<` and `<=` inside `{}` expression blocks get misread as tag + openers. Anything numeric / boolean is hoisted to frontmatter. + ----------------------------------------------------------------- */ + +const lastUpdated = '2026-05-27'; +const noticeWindowDays = 30; +const subscriberEmail = 'subprocessors@warmbly.com'; + +// Real vendors that Warmbly's architecture depends on (per CLAUDE.md). +// Every link points to the vendor's actual security/DPA/privacy page. +type SubRow = { + vendor: string; + purpose: string; + data: string; + region: string; + link: { label: string; href: string }; +}; + +const subprocessors: SubRow[] = [ + { + vendor: 'Amazon Web Services', + purpose: 'Primary infrastructure. Compute, S3 object storage, KMS root of trust for envelope encryption, DynamoDB for encrypted DEK storage, RDS Postgres for relational data, MSK for Kafka.', + data: 'Account data, contacts, sequence content, encrypted mailbox payloads, deliverability events, audit logs.', + region: 'us-east-1, eu-west-1', + link: { label: 'aws.amazon.com/compliance', href: 'https://aws.amazon.com/compliance/' }, + }, + { + vendor: 'Cloudflare', + purpose: 'Edge network, DDoS protection, WAF, marketing-site CDN, and Turnstile CAPTCHA on auth-sensitive routes (login, registration, password reset).', + data: 'Request metadata, IP addresses, Turnstile challenge tokens. No mailbox or campaign content.', + region: 'Global edge', + link: { label: 'cloudflare.com/trust-hub', href: 'https://www.cloudflare.com/trust-hub/' }, + }, + { + vendor: 'Stripe', + purpose: 'Billing, subscription management, tax, and webhook-backed payment events. Card data is collected by Stripe directly via Stripe Elements, never by Warmbly.', + data: 'Billing contact, business name, country, last 4 digits of payment method (tokenized by Stripe).', + region: 'United States, Ireland', + link: { label: 'stripe.com/privacy', href: 'https://stripe.com/privacy' }, + }, + { + vendor: 'Postmark', + purpose: 'Transactional product email only. Account confirmations, password resets, security alerts, billing receipts. Not used for customer campaign sending.', + data: 'Recipient email address (customer staff), message body for the transactional event.', + region: 'United States', + link: { label: 'postmarkapp.com/eu-privacy', href: 'https://postmarkapp.com/eu-privacy' }, + }, + { + vendor: 'Sentry', + purpose: 'Application error monitoring for the backend, worker fleet, and frontend. Stack traces are scrubbed of customer message bodies before transport.', + data: 'Error stack traces, request IDs, user IDs, scrubbed request metadata.', + region: 'United States', + link: { label: 'sentry.io/legal/dpa', href: 'https://sentry.io/legal/dpa/' }, + }, + { + vendor: 'Plain', + purpose: 'Customer support help-desk. Routes inbound customer email and in-app support threads to the team rotation.', + data: 'Support correspondence, customer name and email, workspace ID.', + region: 'European Union, United States', + link: { label: 'plain.com/legal/privacy', href: 'https://www.plain.com/legal/privacy' }, + }, ]; + +// Evaluation criteria. Short and reviewable, not a marketing list. +const criteria = [ + { + icon: 'shield', + title: 'Security posture', + body: 'Vendor must publish a security overview, support TLS in transit, and offer a DPA. We review SOC 2, ISO 27001, or equivalent attestation where the vendor publishes one.', + }, + { + icon: 'globe', + title: 'Data residency', + body: 'For EU customer data we prefer EU processing regions. Where a vendor is US-only, we rely on Standard Contractual Clauses and document the transfer.', + }, + { + icon: 'lock', + title: 'Scope of access', + body: 'A subprocessor only sees the smallest slice of data the integration requires. Mailbox content stays sealed with KMS-backed envelope encryption and is not handed to third parties.', + }, + { + icon: 'cog', + title: 'Operational fit', + body: 'We pick boring, well-operated vendors with public status pages, predictable change communication, and a real incident response process.', + }, +]; + +// "What happens when we add a subprocessor" steps. +const noticeSteps = [ + { + n: '01', + title: `${noticeWindowDays} days advance notice`, + body: 'We update this page and post an in-app banner before any new subprocessor that processes customer Personal Data goes live. Subscribers also receive an email.', + }, + { + n: '02', + title: 'Objection window', + body: `Customers may object on reasonable data-protection grounds during the ${noticeWindowDays}-day window. Send objections to subprocessors@warmbly.com with the affected subprocessor and the basis for the objection.`, + }, + { + n: '03', + title: 'Resolution', + body: 'We work in good faith to resolve objections, which may include scoping the integration, choosing an alternative vendor, or, where the change is material and unresolved, allowing the customer to terminate the affected service.', + }, +]; + +// Previous subprocessors. Empty by design at launch; the layout +// renders an explicit empty state so the section is still legally +// meaningful. +const previous: SubRow[] = []; + +// Counts hoisted for the JSX so the parser does not have to evaluate +// `<` style expressions inline. +const activeCount = subprocessors.length; +const previousCount = previous.length; +const hasPrevious = previousCount > 0; --- - -

- Warmbly engages the third parties listed below to deliver the service. We give 30 days' notice (via this page and an in-app notice) before adding a new subprocessor that processes customer Personal Data. -

+ +
+ -

Active subprocessors

-
- - - - - - - - - - - {rows.map((r) => ( - - {r.map((c, i) => )} - +
+
+
+ Legal + Subprocessors +
+ +

+ The vendors behind
the platform. +

+ +

+ These are the third-party services Warmbly uses to operate the platform. We notify customers of material changes. +

+ +
+ + + Last updated {lastUpdated} + + + {activeCount} active subprocessors + + + See the DPA + + +
+
+
+ + + +
+
+
+
+
+ +
+
+
Subscribe to changes
+

+ Get a {noticeWindowDays}-day advance notice whenever a new subprocessor is added or an existing one changes scope. One email per change, never marketing. +

+
+
+ + + {subscriberEmail} + +
+
+
+ + +
+
+
+
+
Active subprocessors
+

+ Who touches your data, and why. +

+
+

+ Region listed is where the vendor processes Warmbly customer data. Link points to the vendor's own security or DPA page. +

+
+ + +
SubprocessorPurposeRegionCountry
{c}
+ + + + + + + + + + + {subprocessors.map((s) => ( + + + + + + + + ))} + +
VendorPurposeData categoriesRegionReference
+
{s.vendor}
+
{s.purpose}{s.data} + + {s.region} + + + + {s.link.label} + + +
+
+
+ + +
+ {subprocessors.map((s) => ( +
+
+
{s.vendor}
+ {s.region} +
+ +
+
+
Purpose
+
{s.purpose}
+
+
+
Data categories
+
{s.data}
+
+
+ + + {s.link.label} + + +
))} - - -
+
-

Subscribe to changes

-

- Email legal@warmbly.com to be notified of subprocessor changes by email. We send a notice at least 30 days before any change that affects customer data processing. -

- +

+ Links open the vendor's own security or privacy page. Warmbly does not republish or endorse those documents. +

+
+
+ + +
+
+
+
+
How we evaluate
+

+ Boring, well-operated, scoped tight. +

+

+ New subprocessors are added when an integration genuinely needs them, not because a vendor is on a procurement list. Every entry above passed the same review. +

+
+ +
    + {criteria.map((c) => ( +
  • +
    + +
    +
    {c.title}
    +

    {c.body}

    +
  • + ))} +
+
+
+
+ + +
+
+
When this list changes
+

+ {noticeWindowDays} days notice. Real objection window. +

+

+ Adding a subprocessor is a contract change, not a backend tweak. We treat it like one. +

+ +
    + {noticeSteps.map((s) => ( +
  1. +
    {s.n}
    +
    {s.title}
    +

    {s.body}

    +
  2. + ))} +
+ +
+
+ +
+
+ The {noticeWindowDays}-day notice is contractual. It is restated in the Data Processing Addendum section 7. The objection process there controls if there is any conflict with this page. +
+
+
+
+ + +
+
+
+
+
Previous subprocessors
+

+ Vendors we have removed or replaced. +

+
+

+ When a vendor is retired, the row moves here with the date it was removed and the reason. +

+
+ + {hasPrevious ? ( +
+
+ + + + + + + + + + + {previous.map((p) => ( + + + + + + + ))} + +
VendorPurposeRegionRemoved
{p.vendor}{p.purpose}{p.region}{p.data}
+
+
+ ) : ( +
+
+ +
+
No previous subprocessors yet.
+

+ Warmbly has not removed or replaced a subprocessor since this page was first published. When that changes, the retired vendor will appear here with the date and reason. +

+
+ )} +
+
+ + + diff --git a/site/src/pages/terms.astro b/site/src/pages/terms.astro index f4961a10..9bef7e0f 100644 --- a/site/src/pages/terms.astro +++ b/site/src/pages/terms.astro @@ -1,103 +1,421 @@ --- -import Legal from '../layouts/Legal.astro'; +import Layout from '../layouts/Layout.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; + +const lastUpdated = '2026-05-27'; +const effectiveFrom = '2026-05-27'; + +const sections = [ + { id: 'acceptance', num: '01', label: 'Acceptance of terms' }, + { id: 'definitions', num: '02', label: 'Definitions' }, + { id: 'account', num: '03', label: 'Account registration and security' }, + { id: 'acceptable-use', num: '04', label: 'Acceptable use' }, + { id: 'availability', num: '05', label: 'Service availability and changes' }, + { id: 'billing', num: '06', label: 'Subscription, billing, refunds' }, + { id: 'ip', num: '07', label: 'Intellectual property' }, + { id: 'user-content', num: '08', label: 'User content and your data' }, + { id: 'confidentiality', num: '09', label: 'Confidentiality' }, + { id: 'third-party', num: '10', label: 'Third-party services' }, + { id: 'disclaimers', num: '11', label: 'Disclaimers' }, + { id: 'liability', num: '12', label: 'Limitation of liability' }, + { id: 'indemnification', num: '13', label: 'Indemnification' }, + { id: 'termination', num: '14', label: 'Termination' }, + { id: 'governing-law', num: '15', label: 'Governing law and jurisdiction' }, + { id: 'changes', num: '16', label: 'Changes to these terms' }, + { id: 'contact', num: '17', label: 'Contact' }, +]; + +const legalDocs = [ + { label: 'Terms of service', href: '/terms/' }, + { label: 'Privacy policy', href: '/privacy/' }, + { label: 'Acceptable use', href: '/acceptable-use/' }, + { label: 'Data processing addendum', href: '/dpa/' }, + { label: 'Subprocessors', href: '/subprocessors/' }, + { label: 'Cookies', href: '/cookies/' }, +]; --- - -

- These Terms of Service ("Terms") govern your access to and use of the websites, applications, APIs and services provided by Warmbly Labs, Inc. ("Warmbly", "we", "us"). By creating an account or using the services, you agree to these Terms. -

+ +
+ -

1. The service

-

- Warmbly is a cold-email and deliverability platform that helps customers warm up email mailboxes, send sequenced campaigns, manage replies through a unified inbox, and monitor mailbox health. -

+
+
+ + Legal + + The agreement between you and Warmbly +
-

2. Accounts

-
    -
  • You must be at least 18 years old and able to enter binding contracts to use the service.
  • -
  • You are responsible for safeguarding your account credentials.
  • -
  • You are responsible for the activity that occurs under your account, including activity by your team members and integrations you authorise.
  • -
+

+ Terms of service. +

-

3. Subscriptions and billing

-
    -
  • Paid plans are billed monthly or annually in advance.
  • -
  • Charges are mailbox-based — the connected mailbox count on your billing date drives the invoice.
  • -
  • You may cancel at any time; the cancellation takes effect at the end of the current billing period.
  • -
  • Refunds are available within 14 days of a paid purchase if the service did not work for you. After 14 days, charges are non-refundable except where required by law.
  • -
+

+ These terms govern how you use Warmbly. We have written them in plain English so you can read them in one sitting. Read them. +

-

4. Acceptable use

-

- Your use of Warmbly must comply with our Acceptable Use Policy. In particular, you may not use the service to: -

-
    -
  • Send unsolicited bulk email outside the scope of cold outreach permitted in the relevant jurisdiction.
  • -
  • Send mail that violates CAN-SPAM, GDPR, CASL, PECR or any other applicable law.
  • -
  • Forge sender identity or attempt to bypass authentication.
  • -
  • Run mail that promotes deceptive, fraudulent, illegal or harmful content.
  • -
  • Probe, scan or stress-test the platform without our written consent.
  • -
+
+
+ + Last updated + {lastUpdated} +
+
+ + Effective from + {effectiveFrom} +
+
+
+
-

5. Suspension and termination

-

- We may suspend or terminate access if you breach these Terms, violate the Acceptable Use Policy, or generate complaint, bounce or abuse signals that threaten the platform's shared infrastructure. We will, where reasonable, give you notice and an opportunity to cure. -

+ +
+
+
-

6. Intellectual property

-

- Warmbly and its licensors retain all rights in the platform, including its software, designs, documentation and marks. You retain all rights in the content you upload or send through the platform. -

-

- By using the platform, you grant Warmbly the limited rights necessary to deliver the service (storing, processing, transmitting your content and credentials as required). -

+ + -

11. Indemnity

-

- You will defend, indemnify and hold harmless Warmbly from any third-party claim arising from your content, your use of the service in breach of these Terms, or your violation of any applicable law. -

+ +
-

12. Changes to these terms

-

- We may update these Terms. Material changes will be announced via email or in-app notice at least 30 days in advance. Continued use after the effective date constitutes acceptance. -

+ +
+
+ Summary +
+

+ Warmbly is a software service. You bring your mailboxes, your contacts, and your campaigns. We run the infrastructure that warms inboxes, sends mail, and reports on what happened. You agree not to use Warmbly for spam or anything illegal. We agree to keep the lights on, treat your data with care, and tell you before we change anything important. +

+
-

13. Governing law

-

- These Terms are governed by the laws of the State of Delaware, without regard to its conflict of laws rules. Disputes are subject to the exclusive jurisdiction of the state and federal courts located in Wilmington, Delaware. -

+ +
-

14. Contact

-

- Warmbly Labs, Inc. — 1209 N. Orange Street, Wilmington, DE 19801, USA. Email legal@warmbly.com. -

- + +

+ 01 + Acceptance of terms +

+

+ These Terms of Service ("Terms") form a binding agreement between you and Warmbly ("Warmbly", "we", "us"). By creating an account, signing in, or using any part of the Warmbly platform, websites, APIs, dashboards, or worker software (together, the "Service"), you agree to these Terms. +

+

+ If you are accepting on behalf of a company, you confirm that you have authority to bind that company, and "you" then refers to that company. If you do not agree to these Terms, do not use the Service. +

+

+ You must be at least 18 years old and legally able to enter into a contract. +

+ + +

+ 02 + Definitions +

+
    +
  • Account. The login and workspace you create on Warmbly.
  • +
  • Service. The hosted Warmbly product, including the dashboard, API, worker software, and supporting infrastructure.
  • +
  • Customer content. The data you upload or connect, such as contacts, mailbox credentials, campaigns, replies, and message bodies.
  • +
  • Subscription. A paid plan billed on a recurring basis through our payment processor.
  • +
  • Mailbox. An email account you connect to Warmbly for warmup or sending.
  • +
+ + +

+ 03 + Account registration and security +

+

+ You agree to provide accurate information when you register, and to keep that information current. You are responsible for everything that happens under your account, including actions taken by team members or API clients you authorize. +

+

+ You agree to protect your credentials, use a strong password, and enable available account-security features. Notify us promptly at security@warmbly.com if you suspect unauthorized access. +

+

+ We may suspend access to your account if we reasonably believe it has been compromised, until you can confirm it is secure. +

+ + +

+ 04 + Acceptable use +

+

+ Your use of the Service must follow our Acceptable Use Policy. In short, do not use Warmbly to send unsolicited bulk mail outside what is permitted by the law in the relevant jurisdiction, do not forge sender identity, do not attempt to bypass authentication, and do not send content that is fraudulent, deceptive, or illegal. +

+

+ You are also responsible for keeping your sender domain configured correctly, including SPF, DKIM, and DMARC alignment where relevant. +

+

+ We may remove content, throttle traffic, or suspend the account if your use threatens the platform's shared infrastructure or recipient trust. Where it is reasonable, we will tell you what is wrong and give you a chance to fix it before we act. +

+ + +

+ 05 + Service availability and changes +

+

+ We work to keep the Service available, but we do not guarantee uninterrupted access. Maintenance windows, third-party outages, and unexpected incidents will happen. We try to communicate planned downtime in advance through the status page or in-app notice. +

+

+ We may add, change, deprecate, or remove features over time. If a change materially reduces functionality you depend on, we will give reasonable notice through email or an in-app notice before the change takes effect. +

+ + +

+ 06 + Subscription, billing, refunds +

+

Subscriptions

+

+ Paid plans are billed in advance on a recurring basis (monthly or annual, depending on the plan you choose). Subscriptions automatically renew at the end of each billing period until you cancel. +

+

Payment processor

+

+ Payments are processed by Stripe. By providing a payment method, you authorize us, through Stripe, to charge the fees due for your plan. We do not store full card numbers. The complete list of providers that handle data on our behalf is at /subprocessors. +

+

Cancellation

+

+ You can cancel at any time from the dashboard. Cancellation takes effect at the end of the current billing period. You keep access to the paid plan until then. +

+

Refunds

+

+ We offer a 14-day refund on a new paid subscription if the Service has not worked for you. After 14 days, fees are non-refundable except where required by law. Usage-based or overage charges are not refundable. +

+

Taxes

+

+ Prices do not include taxes unless stated. You are responsible for any sales tax, VAT, GST, or similar charges, except for taxes based on our net income. +

+ + +

+ 07 + Intellectual property +

+

+ Warmbly and its licensors own the Service, including its software, design, documentation, trademarks, and the trademarks "Warmbly" and the Warmbly logo. We grant you a limited, non-exclusive, non-transferable, revocable right to access and use the Service during the term of your subscription. +

+

+ You are not granted any other rights, including the right to copy, redistribute, sublicense, or create derivative works from the Service, except where permitted by an open-source license that applies to a specific component. +

+

+ If you send us feedback or suggestions, you grant us a perpetual, worldwide, royalty-free right to use that feedback to improve the Service. We will not identify you as the source without your permission. +

+ + +

+ 08 + User content and your data +

+

+ You retain all rights in your customer content. You grant Warmbly a limited license to host, transmit, process, and store that content as needed to deliver the Service, support your account, prevent abuse, and meet our legal obligations. +

+

+ You confirm that you have the right to use the customer content you upload (including contact lists), that recipients are in scope for the law that applies to your sending, and that you will honor opt-outs and unsubscribe requests promptly. +

+

+ How we handle personal data is explained in our Privacy Policy. If you are a controller of personal data processed through the Service, our Data Processing Addendum applies. +

+ + +

+ 09 + Confidentiality +

+

+ Each party may receive information from the other that is marked confidential, or that a reasonable person would treat as confidential. Each party will protect the other's confidential information with the same care it uses for its own, and will not disclose it except as needed to perform under these Terms or as required by law. +

+

+ Confidential information does not include information that is public through no fault of the receiving party, that the receiving party already had without confidentiality obligations, or that is independently developed. +

+ + +

+ 10 + Third-party services +

+

+ The Service connects to third-party providers, such as email providers (Google, Microsoft, custom SMTP), payment processors, and analytics platforms. Your use of those providers is governed by their own terms and policies, which you accept when you connect them. +

+

+ We are not responsible for the availability, accuracy, or behavior of third-party services. If a third-party provider changes its API or pricing, we will adapt as quickly as we reasonably can. +

+ + +

+ 11 + Disclaimers +

+

+ The Service is provided "as is" and "as available". To the maximum extent permitted by law, Warmbly disclaims all warranties, express or implied, including warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranty that arises from a course of dealing or usage of trade. +

+

+ We do not guarantee a specific deliverability outcome, inbox placement rate, reply rate, or revenue result. Email deliverability depends on authentication, content, recipient behavior, and provider-side reputation, much of which is outside our direct control. +

+

+ Nothing in these Terms limits any warranty or right that cannot be limited under applicable law. +

+ + +

+ 12 + Limitation of liability +

+

+ To the maximum extent permitted by law, Warmbly's total liability for any claim arising out of or related to these Terms or the Service will not exceed the fees you paid to Warmbly in the 12 months before the event that gave rise to the claim. +

+

+ Neither party will be liable for indirect, incidental, consequential, special, exemplary, or punitive damages, or for loss of profits, revenue, goodwill, or data, even if advised of the possibility. +

+

+ These limits do not apply to liability that cannot be limited under applicable law, including liability for gross negligence or willful misconduct where the law does not allow exclusion. +

+ + +

+ 13 + Indemnification +

+

+ You agree to defend, indemnify, and hold harmless Warmbly, its affiliates, and its employees from any third-party claim, loss, or expense (including reasonable legal fees) arising from your customer content, your use of the Service in breach of these Terms, or your violation of applicable law. +

+

+ We will tell you promptly about any claim, give you reasonable control of the defense, and cooperate with you at your expense. You may not settle a claim that imposes any obligation on us without our prior written consent. +

+ + +

+ 14 + Termination +

+

+ You may stop using the Service at any time and cancel your subscription from the dashboard. +

+

+ We may suspend or terminate your access if you breach these Terms or the Acceptable Use Policy, if your use generates complaint, bounce, or abuse signals that threaten the platform's shared infrastructure, or if we are required to do so by law. Where it is reasonable, we will tell you the reason and give you a chance to fix it first. +

+

+ On termination, your right to use the Service ends. We will keep customer content for a reasonable wind-down period (typically up to 30 days) so you can export it, after which we may delete it. Provisions that by their nature should survive termination will survive, including the sections on intellectual property, confidentiality, disclaimers, limitation of liability, indemnification, and governing law. +

+ + +

+ 15 + Governing law and jurisdiction +

+

+ These Terms are governed by the laws of the jurisdiction of Warmbly's principal place of business, without regard to its conflict-of-laws rules. The courts located in that jurisdiction have exclusive jurisdiction over any dispute arising out of or related to these Terms or the Service, except where local consumer-protection law gives you the right to bring a claim in your own jurisdiction. +

+

+ Nothing in this section limits either party's right to seek injunctive relief in any court of competent jurisdiction to protect intellectual-property or confidential information. +

+ + +

+ 16 + Changes to these terms +

+

+ We may update these Terms over time. When we make a material change, we will give at least 30 days notice through email or an in-app notice before the change takes effect. The "Last updated" date at the top of this page always reflects the most recent version. +

+

+ If you keep using the Service after the change takes effect, you accept the updated Terms. If you do not agree, you can stop using the Service and cancel your subscription before the effective date. +

+ + +

+ 17 + Contact +

+

+ Questions about these Terms? Email legal@warmbly.com and we will get back to you. +

+
+ + +
+
+ Last updated {lastUpdated}. Effective {effectiveFrom}. +
+ Back to top +
+ +
+
+
+
+ + + diff --git a/site/src/pages/trust.astro b/site/src/pages/trust.astro index 179b8402..e5e78c47 100644 --- a/site/src/pages/trust.astro +++ b/site/src/pages/trust.astro @@ -1,169 +1,572 @@ --- import Layout from '../layouts/Layout.astro'; -import Cloud from '../components/Cloud.astro'; +import HeroAtmosphere from '../components/HeroAtmosphere.astro'; import Icon from '../components/Icon.astro'; import CTA from '../components/CTA.astro'; -const status = [ - { label: 'API', ok: true, ms: 142 }, - { label: 'Worker fleet', ok: true, ms: 91 }, - { label: 'Webhooks', ok: true, ms: 218 }, - { label: 'Realtime', ok: true, ms: 38 }, - { label: 'Inbox sync', ok: true, ms: 304 }, +/* ----------------------------------------------------------------- + Trust page. Full redesign. + + Source of truth: + - CLAUDE.md, sections "Encryption Model", "Worker Networking + Rules", "Fraud And Abuse Detection", "Sending Safety Policy" + - internal/app/cipher/* + - internal/infrastructure/kms/* + - cmd/worker/main.go + - internal/api/middleware/ratelimit.go + - internal/pkg/captcha/turnstile.go + - internal/repository/pg_warmup.go + + Tone: factual. No fake compliance badges. No fake metrics. + No customer quotes. Roadmap items marked as roadmap. + + Astro parser quirk: any `<` or `<=` comparison inside `{}` blocks + breaks the parser. Hoist them up here. + ----------------------------------------------------------------- */ + +// Posture badges. Honest about what is shipped vs roadmap. +// `state` drives the dot color in the JSX below. +const posture = [ + { + label: 'Open source, auditable', + note: 'Backend, workers, consumer and frontend are public.', + state: 'shipped', + }, + { + label: 'TLS 1.2+ in transit', + note: 'Edge terminates on Cloudflare. Origin requires TLS 1.2 or above.', + state: 'shipped', + }, + { + label: 'Encryption at rest via AWS KMS', + note: 'Per-user data keys, AES-256-GCM application layer.', + state: 'shipped', + }, + { + label: 'GDPR-aligned data handling', + note: 'DPA on request. Subprocessor notice via /subprocessors/.', + state: 'shipped', + }, + { + label: 'SOC 2 Type II', + note: 'Controls in place. Audit window opens with our auditor.', + state: 'roadmap', + }, + { + label: 'ISO 27001', + note: 'Under evaluation. No certification claimed today.', + state: 'roadmap', + }, ]; -const compliance = [ - { name: 'SOC 2 Type II', state: 'In progress', est: 'Q3 2026', tone: 'sky' }, - { name: 'GDPR alignment', state: 'Active', est: '—', tone: 'emerald' }, - { name: 'DPA on request', state: 'Active', est: '48h SLA', tone: 'emerald' }, - { name: 'HIPAA', state: 'Not pursued', est: '—', tone: 'slate' }, +// Encryption flow steps for the KMS visual. +const flow = [ + { + n: '01', + title: 'AWS KMS', + body: 'Customer Master Key lives in KMS and never leaves it. KMS generates a 32-byte data encryption key for each user on first use.', + tag: 'Root of trust', + }, + { + n: '02', + title: 'Per-user DEK', + body: 'The plaintext DEK is returned briefly to the application. The encrypted DEK blob comes back in the same call and is the only long-term copy.', + tag: 'AES-256', + }, + { + n: '03', + title: 'AES-GCM seal', + body: 'Sensitive fields like mailbox tokens and IMAP credentials are sealed with AES-256-GCM using the user DEK, then base64 encoded.', + tag: 'Authenticated', + }, + { + n: '04', + title: 'Ciphertext at rest', + body: 'Ciphertext is stored in Postgres or DynamoDB. The encrypted DEK blob lives in DynamoDB. Plaintext DEKs are cached in Redis with a TTL.', + tag: 'Envelope', + }, ]; +// Two-column "what we collect / what we never collect". +const collect = [ + 'Account profile: email, name, organization.', + 'Mailbox credentials, encrypted at rest: OAuth tokens, IMAP and SMTP secrets.', + 'Sent and received message bodies for connected mailboxes, encrypted at rest.', + 'Recipient lists you upload or sync.', + 'Deliverability signals: bounces, complaints, replies, opens, clicks, suppression.', + 'Billing metadata via Stripe. Card data never touches our servers.', + 'Operational logs scoped to debugging. Customer message bodies are not logged.', +]; + +const neverCollect = [ + 'Plaintext passwords. We store password hashes only, and never store mailbox passwords in plaintext.', + 'Plaintext DEKs at rest. The encrypted DEK is what persists.', + 'Card numbers, CVCs, or full PAN. Stripe holds the payment instrument.', + 'Recipient browsing or off-platform behavior. Tracking covers your campaign mail only.', + 'Customer message bodies inside error reports. Sentry payloads are scrubbed.', + 'Cross-customer data sharing. Tenancy is enforced at the query layer.', +]; + +// Subprocessor summary. Full list lives at /subprocessors/. const subs = [ - ['AWS', 'Compute, storage, KMS, DynamoDB, S3', 'us-east-1 · eu-west-1'], - ['Cloudflare', 'Edge, DDoS, Turnstile CAPTCHA', 'Global'], - ['Stripe', 'Billing', 'US'], - ['PostHog', 'Product analytics · workspace-owner-visible', 'EU'], - ['Slack', 'Customer support escalations', 'US'], - ['Linear', 'Issue tracking', 'US'], - ['Sentry', 'Error reporting', 'US'], - ['Postmark', 'Transactional mail · account emails only', 'US'], + { name: 'AWS', use: 'Compute, KMS, DynamoDB, S3, MSK, RDS', region: 'us-east-1, eu-west-1' }, + { name: 'Cloudflare', use: 'Edge, DDoS, Turnstile CAPTCHA', region: 'Global' }, + { name: 'Stripe', use: 'Billing and tax', region: 'United States' }, + { name: 'Postmark', use: 'Transactional account email', region: 'United States' }, + { name: 'Sentry', use: 'Error reporting, content scrubbed', region: 'United States' }, + { name: 'PostHog', use: 'Product analytics, owner-disable-able', region: 'Germany' }, +]; + +// Abuse and safety controls. Numbers below come straight from the repo. +const safety = [ + { + icon: 'shield', + title: 'Warmup-token verification', + body: 'Every warmup email carries a verification token. Missing, expired or malformed tokens are recorded against the sending mailbox.', + }, + { + icon: 'warning', + title: 'Auto-block thresholds', + body: 'Three or more invalid warmup-token attempts in 24 hours, or a spam score above 50, removes a mailbox from the shared warmup pool.', + }, + { + icon: 'filter', + title: 'Suppression hygiene', + body: 'Bounces, complaints and unsubscribes are written to suppression and enforced at send time. Campaigns skip suppressed recipients automatically.', + }, + { + icon: 'lock', + title: 'Turnstile on auth surfaces', + body: 'Cloudflare Turnstile gates login, registration, password reset and confirmation flows. Challenge freshness and remote IP are validated server-side.', + }, + { + icon: 'cpu', + title: 'Per-user rate limiting', + body: 'API and WebSocket traffic are rate-limited per user against Redis-backed counters, with category-specific budgets per plan.', + }, + { + icon: 'check', + title: 'Event idempotency', + body: 'Tracking events deduplicate via in-memory and persistent caches. Deliverability events and Stripe webhooks carry idempotency keys.', + }, +]; + +// Worker boundary properties. +const workerBoundary = [ + { k: 'PostgreSQL', v: 'never connected', tone: 'never' }, + { k: 'Kafka', v: 'commands in, results out', tone: 'yes' }, + { k: 'AWS KMS', v: 'envelope decryption only', tone: 'yes' }, + { k: 'DynamoDB', v: 'encrypted DEK lookup', tone: 'yes' }, + { k: 'S3', v: 'object storage only', tone: 'yes' }, + { k: 'Redis', v: 'plaintext DEK cache, TTL', tone: 'yes' }, +]; + +// Disclosure SLAs. Honest, conservative. +const disclosure = [ + { k: 'Acknowledge', v: 'within 1 business day' }, + { k: 'Initial triage', v: 'within 3 business days' }, + { k: 'Status updates', v: 'every 7 days until resolved' }, + { k: 'Public credit', v: 'on request, after fix shipped' }, ]; --- - -
-
- -
-
-
-
-
Trust
-

- Operational truth, in one place. -

-

- Uptime, security posture, subprocessors and compliance. Published, not promised. -

-
- + +
+ + +
- -
+ +
-
Compliance
-

Where we are, honestly.

+
-
- {compliance.map((c) => ( -
-
-
{c.name}
- - - {c.state} - -
-
{c.est}
+
+
+
Posture
+
What we have shipped, and what is on the roadmap.
+
+ + Subprocessors + +
+ +
+ {posture.map((p, i) => { + const shipped = p.state === 'shipped'; + const dot = shipped ? 'bg-emerald-500' : 'bg-amber-500'; + const pillBg = shipped ? 'bg-emerald-50 text-emerald-700' : 'bg-amber-50 text-amber-700'; + const stateLabel = shipped ? 'Shipped' : 'Roadmap'; + const borderRight = (i % 3) !== 2 ? 'lg:border-r' : ''; + const borderBottomLg = i < 3 ? 'lg:border-b' : ''; + return ( +
+
+
{p.label}
+ + + {stateLabel} + +
+

{p.note}

+
+ ); + })} +
+
+ +

+ We do not claim certifications we do not hold. SOC 2 and ISO 27001 are listed as roadmap items because they are honestly roadmap items. +

+
+
+ + +
+
+
+
Encryption model
+

+ Envelope encryption, with AWS KMS at the root. +

+

+ Mailbox tokens, IMAP credentials and other sensitive fields are sealed at the application layer before they touch a database. The key that does the sealing is itself protected by KMS, so a database snapshot in isolation is not enough to read a single byte of plaintext. +

+
+ + +
+
+ {flow.map((f, i) => { + const isLast = i === 3; + const isFirstRow = i < 2; + const showArrow = i < 3; + const borderRight = isLast ? '' : 'md:border-r'; + const borderBottom = isFirstRow ? 'border-b md:border-b-0' : (i === 2 ? 'border-b md:border-b-0' : ''); + return ( +
+
+
{f.n}
+ {f.tag} +
+
{f.title}
+

{f.body}

+ {showArrow && ( + + )} +
+ ); + })} +
+ + +
+ Plaintext DEK cached in Redis with TTL + Encrypted DEK persisted in DynamoDB + AES-256-GCM authenticated encryption +
+
+ + +
+ {[ + 'internal/app/cipher/cipher.go', + 'internal/app/cipher/encrypt.go', + 'internal/infrastructure/kms/encryption.go', + 'internal/repository/dynamo_user_encrypted_keys.go', + ].map((p) => ( +
+ + {p}
))}
- -
-
+ +
+
-
Security posture
-

Envelope encryption with AWS KMS.

-

- Per-user data encryption keys. Encrypted DEK in DynamoDB, plaintext briefly cached. Workers never touch Postgres. +

Worker boundary
+

+ Workers do not touch your database. +

+

+ Workers are the execution plane. They send and sync mail across many machines with separate network identities. They receive commands from Kafka and publish results to Kafka. They never open a PostgreSQL connection.

- - Request the security pack +

+ That boundary matters. A worker compromise does not expose the relational store. Sensitive payloads stay encrypted in transit and are only opened against KMS-backed primitives. +

+
+ Read the architecture
-
- {[ - 'AES-256-GCM with KMS-generated DEKs', - 'Per-user envelope encryption with cached decryption', - 'Cloudflare Turnstile on every auth-sensitive route', - 'Rate-limited APIs and WebSocket realtime', - 'Idempotency keys + dedupe on tracking and webhooks', - 'Audit log on all admin actions', - ].map((b) => ( -
- - {b} + +
+
+ +
cmd/worker · network surface
+
+
+ {workerBoundary.map((row) => { + const isNever = row.tone === 'never'; + const dot = isNever ? 'bg-rose-500' : 'bg-emerald-500'; + const pill = isNever ? 'bg-rose-50 text-rose-700' : 'bg-emerald-50 text-emerald-700'; + return ( +
+
{row.k}
+
{row.v}
+ + + {isNever ? 'never' : 'allowed'} + +
+ ); + })} +
+
+
+
+ + +
+
+
+
Data handling
+

+ What we hold, and what we refuse to hold. +

+

+ Specifics, not slogans. If you do not see something on the left, we are probably not collecting it. If something on the right ever moves, we will say so on /changelog/. +

+
+ +
+
+
+ +
What we collect
+
+
    + {collect.map((c) => ( +
  • + + {c} +
  • + ))} +
+
+ +
+
+ +
What we never collect
+
+
    + {neverCollect.map((c) => ( +
  • + + {c} +
  • + ))} +
+
+
+
+
+ + +
+
+
+
Abuse and safety
+

+ Layered controls, not one brittle gate. +

+

+ A cold email platform is only as safe as the slowest line of defense. We block early, dedupe everywhere, and keep an audit trail of admin actions. +

+
+ +
+ {safety.map((s) => ( +
+
+ +
+
{s.title}
+

{s.body}

))}
- -
+ +
-
Subprocessors
-

Who touches your data.

+
+
+
Subprocessors
+

Who else touches your data.

+

+ A short summary of the third parties Warmbly engages. The full list, with regions and country of processing, lives at /subprocessors/. +

+
+ + Full subprocessor list + +
-
-
+
+
Subprocessor
Purpose
-
Location
+
Region
- {subs.map(([s, p, l]) => ( + {subs.map((s) => (
-
{s}
-
{p}
-
{l}
+
{s.name}
+
{s.use}
+
{s.region}
))}
+ +

+ We give 30 days notice before adding any subprocessor that processes customer Personal Data. +

+
+
+ + +
+
+
+
Responsible disclosure
+

+ Found something. Tell us first. +

+

+ We welcome reports from security researchers and operators. Send a description, a proof of concept, and any logs to security@warmbly.com. We will acknowledge quickly, triage in the open, and keep you posted until the fix ships. +

+

+ Please avoid testing that degrades service for other customers, accesses data that is not your own, or relies on social engineering of employees or contractors. Good faith research is welcome. +

+ + +
+ +
+
+ Response SLA +
+
+ {disclosure.map((d) => ( +
+
{d.k}
+
{d.v}
+
+ ))} +
+
+
+
+ + +
+
+ + \ No newline at end of file