mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-10 00:04:27 +00:00
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).
This commit is contained in:
+424
-68
@@ -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' },
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="About · Warmbly"
|
||||
description="Warmbly is a small team building the deliverability platform we wished existed when we ran cold email at our previous companies."
|
||||
description="Warmbly is an open-source email warmup and cold outreach platform. Control plane and distributed workers. Per-mailbox safety. Public roadmap."
|
||||
>
|
||||
<!-- HERO: editorial · large statement -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[420px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
<div class="absolute -bottom-12 left-[-60px] w-[300px] opacity-22 pointer-events-none cloud-drift cloud-2" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={3} opacity={1} />
|
||||
</div>
|
||||
<!-- ============================================================
|
||||
HERO · HeroAtmosphere (matches sending, warmup, developers, roadmap)
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<div class="container-page relative pt-16 md:pt-24 pb-20 md:pb-28">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-4">About</div>
|
||||
<h1 class="text-[36px] sm:text-[52px] md:text-[68px] font-semibold tracking-[-0.03em] leading-[1.02] text-white max-w-4xl">
|
||||
We are building the platform we wished existed.
|
||||
<div class="container-page relative pt-16 md:pt-24 pb-44 md:pb-56 text-center">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
About
|
||||
</span>
|
||||
Open source. Apache 2.0. Public roadmap.
|
||||
</div>
|
||||
|
||||
<h1 class="mt-8 md:mt-10 text-[44px] sm:text-6xl md:text-[72px] lg:text-[80px] font-semibold tracking-[-0.04em] leading-[0.98] text-white max-w-4xl mx-auto">
|
||||
The cold outreach<br/>platform you can read.
|
||||
</h1>
|
||||
<p class="mt-6 text-[17px] md:text-[19px] text-white/75 max-w-2xl leading-relaxed">
|
||||
Warmbly was started after one too many quarters watching deliverability collapse on tools that rewarded sending more, not sending well.
|
||||
<p class="mt-6 text-[17px] md:text-[19px] text-white/80 max-w-2xl mx-auto leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="https://github.com/warmbly/warmbly" class="inline-flex h-11 px-5 items-center gap-2 rounded-[10px] text-[14.5px] font-semibold bg-white hover:bg-white hover:-translate-y-0.5 hover:shadow-[0_12px_28px_-6px_rgba(0,0,0,0.3)] transition-all duration-200 ease-out shadow-[0_4px_14px_-2px_rgba(0,0,0,0.18)]">
|
||||
<span style="color:#075985;">Star on GitHub</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#075985" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M7 7h10v10"/><path d="M7 17 17 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#why" class="inline-flex h-11 px-5 items-center rounded-[10px] text-[14.5px] font-medium bg-white/10 backdrop-blur ring-1 ring-white/40 text-white hover:bg-white/20 hover:-translate-y-0.5 transition-all duration-200 ease-out">
|
||||
Read the story
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ESSAY: single column, narrative, pull-quote in the margin -->
|
||||
<section class="py-16 md:py-24">
|
||||
<div class="container-page max-w-3xl">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-4">Why we built this</div>
|
||||
|
||||
<div class="space-y-5 text-[16.5px] leading-[1.7] text-foreground/85">
|
||||
<!-- ============================================================
|
||||
FLOATING FACT CARD · positioning + at-a-glance facts
|
||||
============================================================ -->
|
||||
<section class="relative -mt-36 md:-mt-44 pb-16 md:pb-24 z-10">
|
||||
<div class="container-page">
|
||||
<div class="rounded-[18px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden shadow-[0_40px_90px_-30px_rgba(2,32,71,0.35),0_12px_30px_-12px_rgba(15,23,42,0.12)]">
|
||||
<div class="px-6 py-3 border-b border-[color:var(--border)] flex items-baseline justify-between gap-3">
|
||||
<div class="flex items-baseline gap-3">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground">warmbly / warmbly</div>
|
||||
<span class="text-[11.5px] text-foreground/55">An open-source email warmup and cold outreach platform.</span>
|
||||
</div>
|
||||
<span class="inline-flex items-center gap-1.5 h-6 px-2 rounded-md text-[10.5px] font-mono uppercase tracking-[0.12em] bg-emerald-50 text-emerald-700">
|
||||
<span class="w-1 h-1 rounded-full bg-emerald-500"></span>
|
||||
public
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-3 lg:grid-cols-6 divide-y md:divide-y-0 md:divide-x divide-[color:var(--border)]">
|
||||
{facts.map((f) => (
|
||||
<div class="px-6 py-5">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">{f.k}</div>
|
||||
<div class="mt-1.5 text-[17px] font-semibold tracking-[-0.01em] text-heading font-mono">{f.v}</div>
|
||||
<div class="mt-1.5 text-[11.5px] text-foreground/60 leading-snug">{f.why}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 text-center text-[11.5px] font-mono text-muted-foreground">
|
||||
github.com/warmbly/warmbly
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ============================================================
|
||||
WHY WARMBLY EXISTS · long-form essay, single column
|
||||
============================================================ -->
|
||||
<section id="why" class="bg-white border-y border-[color:var(--border)] py-20 md:py-28 scroll-mt-4">
|
||||
<div class="container-page grid lg:grid-cols-[1fr_2fr] gap-12 lg:gap-20">
|
||||
<div class="lg:sticky lg:top-24 self-start">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Why Warmbly exists</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Cold outreach deserves an open engine.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5 text-[16px] leading-[1.75] text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<blockquote class="border-l-2 border-[color:var(--sky-6)] pl-5 my-8 text-[18px] md:text-[20px] font-semibold tracking-[-0.015em] text-heading leading-[1.4]">
|
||||
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.
|
||||
<blockquote class="border-l-2 border-[#0284c7] pl-5 my-8 text-[19px] md:text-[21px] font-semibold tracking-[-0.015em] text-heading leading-[1.4]">
|
||||
When deliverability breaks on a closed platform, you cannot read the code that broke it. You can only ask support, then wait.
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- BELIEFS: dense 2-col mono-numbered list -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-24">
|
||||
|
||||
<!-- ============================================================
|
||||
PLATFORM SHAPE · control plane vs execution plane
|
||||
============================================================ -->
|
||||
<section class="border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="grid lg:grid-cols-[1fr_2fr] gap-12 lg:gap-20">
|
||||
<div class="lg:sticky lg:top-24 self-start">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">What we believe</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Six rules. Hard to bend.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-[color:var(--border)]">
|
||||
{beliefs.map(([t, b], i) => (
|
||||
<div class="py-5 grid grid-cols-[40px_1fr] gap-4 first:pt-0">
|
||||
<div class="text-[11px] font-mono text-muted-foreground pt-1">{String(i + 1).padStart(2, '0')}</div>
|
||||
<div>
|
||||
<div class="text-[15.5px] font-semibold text-heading tracking-[-0.01em]">{t}</div>
|
||||
<p class="mt-1.5 text-[14px] text-foreground/75 leading-relaxed">{b}</p>
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Platform shape</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Two planes. One rule per lane.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-5">
|
||||
{planes.map((p) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden">
|
||||
<div class="h-1" style={`background:${p.color}`}></div>
|
||||
<div class="p-6">
|
||||
<div class="flex items-baseline justify-between gap-3 mb-3">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono" style={`color:${p.accent}`}>{p.tag}</div>
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded text-[10.5px] font-mono text-foreground/55 bg-[color:var(--surface-1)] ring-1 ring-[color:var(--border)]">
|
||||
{p.parts.length} components
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-[19px] font-semibold tracking-[-0.02em] text-heading">{p.title}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/70 leading-relaxed">{p.body}</p>
|
||||
<div class="mt-4 pt-4 border-t border-[color:var(--border)] flex flex-wrap gap-1.5">
|
||||
{p.parts.map((part) => (
|
||||
<span class="inline-flex items-center h-6 px-2 rounded text-[11px] font-mono text-foreground/70 bg-[color:var(--surface-1)] ring-1 ring-[color:var(--border)]">
|
||||
{part}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Kafka spine annotation -->
|
||||
<div class="mt-6 rounded-[12px] bg-[color:var(--surface-1)]/60 ring-1 ring-[color:var(--border)] px-6 py-4 flex flex-wrap items-baseline justify-between gap-3">
|
||||
<div class="flex items-baseline gap-3">
|
||||
<span class="inline-flex items-center h-5 px-2 rounded text-[10.5px] font-mono uppercase tracking-[0.14em] bg-[#0c1224] text-white/90">Kafka spine</span>
|
||||
<span class="text-[13px] text-foreground/75">Per-worker command and result topics. Replayable. The only thing connecting the two planes.</span>
|
||||
</div>
|
||||
<span class="text-[11.5px] font-mono text-muted-foreground">worker.commands.* · worker.results.*</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ============================================================
|
||||
PRINCIPLES · six rules. numbered. monospace prefix.
|
||||
============================================================ -->
|
||||
<section class="bg-[color:var(--surface-1)]/40 border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Build philosophy</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Six rules we will not bend.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]">
|
||||
{principles.map((p) => (
|
||||
<div class="bg-white p-6 md:p-7">
|
||||
<div class="flex items-baseline gap-4">
|
||||
<span class="font-mono text-[11px] text-[#0284c7] font-semibold tracking-[0.08em]">{p.n}</span>
|
||||
<div class="flex-1">
|
||||
<div class="text-[16px] md:text-[17px] font-semibold tracking-[-0.015em] text-heading">{p.title}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/70 leading-relaxed">{p.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ============================================================
|
||||
POSITIONING · Warmbly vs closed-source competitors
|
||||
============================================================ -->
|
||||
<section class="border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">What is different</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Open engine versus closed engine.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden">
|
||||
<div class="grid grid-cols-[minmax(0,1.1fr)_minmax(0,1.6fr)_minmax(0,1.6fr)] px-6 py-3 bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground">
|
||||
<div></div>
|
||||
<div class="flex items-center gap-2 text-[#0369a1]">
|
||||
<span class="w-2 h-2 rounded-full bg-[#0284c7]"></span>
|
||||
Warmbly
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full bg-slate-400"></span>
|
||||
Closed-source suites
|
||||
</div>
|
||||
</div>
|
||||
{positioning.map((row, i) => (
|
||||
<div class={`grid grid-cols-[minmax(0,1.1fr)_minmax(0,1.6fr)_minmax(0,1.6fr)] px-6 py-4 items-baseline ${i < positioning.length - 1 ? 'border-b border-[color:var(--border)]' : ''}`}>
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.14em] text-foreground/55">{row.label}</div>
|
||||
<div class="text-[13.5px] text-heading font-medium pr-4">{row.warmbly}</div>
|
||||
<div class="text-[13.5px] text-foreground/65 pr-4">{row.closed}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-[11.5px] font-mono text-muted-foreground">
|
||||
Comparison reflects how Warmbly is built. We do not speak for any other vendor's roadmap or pricing.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ============================================================
|
||||
REPO SURFACE · what is in the repo
|
||||
============================================================ -->
|
||||
<section class="bg-[color:var(--surface-1)]/40 border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-10">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Repo surface</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
What is in the repository.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/70 leading-relaxed">
|
||||
The full platform, top-level. Backend, consumer, workers, tracking, realtime, web, and the encryption primitives that hold them together.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-[#0c1224] ring-1 ring-white/10 overflow-hidden">
|
||||
<div class="px-5 py-2.5 border-b border-white/10 flex items-center justify-between text-[10.5px] font-mono text-white/55">
|
||||
<span>warmbly/warmbly · main</span>
|
||||
<span>{surface.length} top-level paths</span>
|
||||
</div>
|
||||
<div class="divide-y divide-white/5">
|
||||
{surface.map((s) => (
|
||||
<div class="grid grid-cols-[200px_1fr] gap-4 px-5 py-3.5 items-baseline">
|
||||
<div class="font-mono text-[12.5px] text-[#7dd3fc]">{s.path}</div>
|
||||
<div class="text-[12.5px] text-white/70 leading-snug">{s.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -96,28 +395,85 @@ const beliefs = [
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- LINKS: small footer-style links row -->
|
||||
<section class="py-16 md:py-20">
|
||||
|
||||
<!-- ============================================================
|
||||
THE TEAM · deliberately small, deliberately public
|
||||
============================================================ -->
|
||||
<section class="border-b border-[color:var(--border)] py-20 md:py-24">
|
||||
<div class="container-page grid lg:grid-cols-[1fr_2fr] gap-12 lg:gap-20">
|
||||
<div class="lg:sticky lg:top-24 self-start">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">The team</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Small team. Public roadmap. Open source.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5 text-[15.5px] leading-[1.75] text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
You can follow the work in public. Open issues, pull requests, the roadmap board, the changelog. Everything that matters is visible.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 grid sm:grid-cols-3 gap-3">
|
||||
<a href="/roadmap/" class="group rounded-[12px] bg-white ring-1 ring-[color:var(--border)] hover:ring-[#0284c7]/40 transition-all p-5 block">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground">Roadmap</div>
|
||||
<div class="mt-2 text-[14.5px] font-semibold text-heading">What is next</div>
|
||||
<div class="mt-1 text-[12px] text-foreground/60">Shipped, in progress, researched.</div>
|
||||
</a>
|
||||
<a href="/changelog/" class="group rounded-[12px] bg-white ring-1 ring-[color:var(--border)] hover:ring-[#0284c7]/40 transition-all p-5 block">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground">Changelog</div>
|
||||
<div class="mt-2 text-[14.5px] font-semibold text-heading">What just shipped</div>
|
||||
<div class="mt-1 text-[12px] text-foreground/60">Every release, dated, plain wording.</div>
|
||||
</a>
|
||||
<a href="https://github.com/warmbly/warmbly" class="group rounded-[12px] bg-white ring-1 ring-[color:var(--border)] hover:ring-[#0284c7]/40 transition-all p-5 block">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground">GitHub</div>
|
||||
<div class="mt-2 text-[14.5px] font-semibold text-heading">The repository</div>
|
||||
<div class="mt-1 text-[12px] text-foreground/60">Source, issues, pull requests.</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ============================================================
|
||||
GET INVOLVED · final link row
|
||||
============================================================ -->
|
||||
<section class="bg-[color:var(--surface-1)]/40 py-20 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-6">More about us</div>
|
||||
<div class="max-w-2xl mb-10">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Get involved</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Read the engine. Run the engine. Improve the engine.
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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) => (
|
||||
<a href={l.href} class="group flex items-center justify-between p-5 rounded-[12px] bg-white ring-1 ring-[color:var(--border)] hover:ring-[color:var(--sky-6)]/40 transition-colors" style="box-shadow: var(--shadow-sm);">
|
||||
{involve.map((l) => (
|
||||
<a href={l.href} class="group flex items-center justify-between p-5 rounded-[12px] bg-white ring-1 ring-[color:var(--border)] hover:ring-[#0284c7]/40 hover:-translate-y-0.5 transition-all duration-200">
|
||||
<div>
|
||||
<div class="text-[14.5px] font-semibold text-heading">{l.label}</div>
|
||||
<div class="text-[12.5px] text-muted-foreground mt-0.5">{l.note}</div>
|
||||
<div class="text-[12px] text-foreground/60 mt-0.5">{l.note}</div>
|
||||
</div>
|
||||
<Icon name="arrowUpRight" size={14} class="text-muted-foreground group-hover:text-[color:var(--brand)] group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-all" />
|
||||
<Icon name={l.icon} size={14} class="text-muted-foreground group-hover:text-[#0284c7] group-hover:translate-x-0.5 transition-all" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA />
|
||||
|
||||
<CTA
|
||||
title="The open engine for cold outreach."
|
||||
description="Read the source. Run it yourself. Or start sending today on the hosted platform."
|
||||
primaryLabel="Start free"
|
||||
primaryHref="https://app.warmbly.com/register"
|
||||
secondaryLabel="View on GitHub"
|
||||
secondaryHref="https://github.com/warmbly/warmbly"
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
@@ -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.',
|
||||
},
|
||||
];
|
||||
---
|
||||
<Legal
|
||||
title="Acceptable Use Policy"
|
||||
description="The conduct Warmbly expects from customers and the conduct that puts shared infrastructure — and your account — at risk."
|
||||
effective="2026-05-01"
|
||||
<Layout
|
||||
title="Acceptable Use Policy · Warmbly"
|
||||
description="The conduct Warmbly expects from customers. Strong, opinionated rules for legitimate B2B cold outreach, and a hard line against abuse."
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
HERO · HeroAtmosphere, shorter than product pages
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<h2>1. Prohibited content</h2>
|
||||
<p>You may not use the service to send mail that:</p>
|
||||
<ul>
|
||||
<li>Is fraudulent, deceptive, or promotes phishing, money mules, or payment scams.</li>
|
||||
<li>Impersonates a person, brand or organisation you have no authority to represent.</li>
|
||||
<li>Promotes illegal activity, including controlled substances, illegal gambling, illegal weapons, malware or hacking services.</li>
|
||||
<li>Targets minors with marketing.</li>
|
||||
<li>Contains sexually explicit content or solicits sexual services.</li>
|
||||
<li>Promotes hate, violence, or discrimination on the basis of protected characteristics.</li>
|
||||
<li>Distributes copyrighted material without the rights holder's permission.</li>
|
||||
<li>Contains malware, exploit code, or links to known phishing sites.</li>
|
||||
</ul>
|
||||
<div class="container-page relative pt-16 md:pt-20 pb-16 md:pb-20">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Legal
|
||||
</span>
|
||||
Acceptable Use Policy
|
||||
</div>
|
||||
|
||||
<h2>2. Prohibited sending behaviour</h2>
|
||||
<ul>
|
||||
<li>Sending to purchased or scraped lists where you have no documented basis to contact the recipient.</li>
|
||||
<li>Sending to recipients who have asked you to stop.</li>
|
||||
<li>Sending bulk consumer marketing without consent in jurisdictions where consent is required.</li>
|
||||
<li>Forging sender identity, bypassing authentication, or sending from a domain you don't own.</li>
|
||||
<li>Sending high-volume mail from a single mailbox in violation of platform caps.</li>
|
||||
<li>Attempting to evade reputation enforcement by churning mailboxes after quarantine.</li>
|
||||
<li>Aggregating multiple customer workspaces under one account to bypass billing.</li>
|
||||
</ul>
|
||||
<h1 class="mt-7 md:mt-9 text-[40px] sm:text-5xl md:text-[64px] lg:text-[72px] font-semibold tracking-[-0.035em] leading-[1.0] text-white max-w-4xl">
|
||||
Acceptable use.
|
||||
</h1>
|
||||
<p class="mt-5 text-[16.5px] md:text-[18px] text-white/80 max-w-2xl leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h2>3. Required practices</h2>
|
||||
<ul>
|
||||
<li>Configure SPF, DKIM and DMARC correctly for every sending domain.</li>
|
||||
<li>Include a real physical address in your sender profile.</li>
|
||||
<li>Provide a clear opt-out mechanism (reply or one-click) in every message that requires one.</li>
|
||||
<li>Honour opt-outs immediately, not "within 10 days".</li>
|
||||
<li>Maintain a documented basis for contacting each recipient (legitimate interest, consent, existing relationship).</li>
|
||||
<li>Use plain text or minimal HTML; avoid image-only bodies and tracking-pixel-only campaigns.</li>
|
||||
<li>Send at sensible volumes within platform caps; do not attempt to override them.</li>
|
||||
</ul>
|
||||
<div class="mt-7 flex flex-wrap items-center gap-3 text-[12px] font-mono">
|
||||
<span class="inline-flex items-center gap-2 h-7 px-3 rounded-full bg-white/10 ring-1 ring-white/25 text-white/90">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-300"></span>
|
||||
Last updated {lastUpdated}
|
||||
</span>
|
||||
<a href="#enforcement" class="inline-flex items-center gap-1.5 h-7 px-3 rounded-full bg-white/10 ring-1 ring-white/25 text-white/90 hover:bg-white/20 transition-colors">
|
||||
Enforcement ladder
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
|
||||
</a>
|
||||
<a href="mailto:abuse@warmbly.com" class="inline-flex items-center gap-1.5 h-7 px-3 rounded-full bg-white/10 ring-1 ring-white/25 text-white/90 hover:bg-white/20 transition-colors">
|
||||
abuse@warmbly.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>4. Platform safety</h2>
|
||||
<ul>
|
||||
<li>Do not probe, scan, stress-test or attempt to penetrate the platform without our written consent.</li>
|
||||
<li>Do not interfere with other customers' use of the service.</li>
|
||||
<li>Do not use the API to scrape data you would not otherwise be entitled to access.</li>
|
||||
<li>Do not attempt to identify other customers' contacts, workspaces or accounts.</li>
|
||||
</ul>
|
||||
<!-- ============================================================
|
||||
RULE STRIP · "Warmbly is for ..."
|
||||
============================================================ -->
|
||||
<section class="border-y border-[color:var(--border)] bg-white">
|
||||
<div class="container-page py-6 md:py-7">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-3 items-center">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground lg:col-span-4 lg:mb-1">
|
||||
Warmbly is for
|
||||
</div>
|
||||
{ruleStrip.map((line) => (
|
||||
<div class="flex items-start gap-2.5">
|
||||
<span class="mt-2 inline-flex w-1.5 h-1.5 rounded-full bg-[color:var(--brand)] shrink-0"></span>
|
||||
<span class="text-[13.5px] text-foreground/85 leading-snug">{line}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>5. Enforcement</h2>
|
||||
<p>
|
||||
Violations of this AUP may result in:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Increased monitoring of the relevant mailbox or workspace.</li>
|
||||
<li>Throttling of send rate.</li>
|
||||
<li>Quarantine of mailboxes from shared pools.</li>
|
||||
<li>Suspension of the workspace pending review.</li>
|
||||
<li>Termination of the account.</li>
|
||||
<li>In serious cases, reporting to law enforcement or affected third parties.</li>
|
||||
</ul>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
BODY · sticky sidebar TOC + main content column
|
||||
============================================================ -->
|
||||
<section class="bg-[color:var(--surface-1)]/30 py-14 md:py-20">
|
||||
<div class="container-page grid grid-cols-1 lg:grid-cols-[240px_minmax(0,1fr)] gap-10 lg:gap-16">
|
||||
|
||||
<h2>6. Report a violation</h2>
|
||||
<p>
|
||||
To report suspected abuse, email <a href="mailto:abuse@warmbly.com">abuse@warmbly.com</a>. Include the relevant message headers if you can.
|
||||
</p>
|
||||
</Legal>
|
||||
<!-- ===== Sidebar ===== -->
|
||||
<aside class="lg:sticky lg:top-20 self-start space-y-8">
|
||||
<div>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">On this page</div>
|
||||
<ul class="space-y-0.5">
|
||||
{toc.map((t) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${t.id}`}
|
||||
class="block text-[13px] -mx-2 px-2 py-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-[color:var(--surface-2)]/70 transition-colors"
|
||||
>
|
||||
{t.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 border-t border-[color:var(--border)]">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">Other documents</div>
|
||||
<ul class="space-y-0.5">
|
||||
{docs.map((d) => (
|
||||
<li>
|
||||
<a
|
||||
href={d.href}
|
||||
class={
|
||||
'block text-[13px] -mx-2 px-2 py-1.5 rounded-md transition-colors ' +
|
||||
(path === d.href
|
||||
? 'bg-[color:var(--surface-2)] text-foreground font-medium'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-[color:var(--surface-2)]/60')
|
||||
}
|
||||
>
|
||||
{d.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 border-t border-[color:var(--border)]">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-2">Report abuse</div>
|
||||
<a href="mailto:abuse@warmbly.com" class="text-[13px] text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] font-medium">
|
||||
abuse@warmbly.com
|
||||
</a>
|
||||
<p class="mt-2 text-[12px] text-muted-foreground leading-relaxed">
|
||||
Include headers if you can. Reviewed within one business day.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ===== Main column ===== -->
|
||||
<article class="max-w-3xl space-y-14">
|
||||
|
||||
<!-- ===== 1. Scope ===== -->
|
||||
<section id="scope" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 1</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Scope</h2>
|
||||
<div class="mt-4 space-y-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
The rules in this document sit on top of the <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]" href="/terms/">Terms of Service</a>. If something here looks looser than the Terms, the Terms win. If something here looks stricter, the stricter rule applies.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 2. What Warmbly is for ===== -->
|
||||
<section id="for" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 2</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">What Warmbly is for</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
Warmbly is a control plane for serious B2B email programs. The intended use cases are narrow and named.
|
||||
</p>
|
||||
<div class="mt-5 grid sm:grid-cols-2 gap-3">
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-5">
|
||||
<div class="inline-flex items-center justify-center w-9 h-9 rounded-[9px] bg-sky-50 text-[color:var(--brand-strong)] ring-1 ring-sky-100">
|
||||
<Icon name="send" size={16} />
|
||||
</div>
|
||||
<div class="mt-3 text-[14.5px] font-semibold text-heading">Legitimate B2B cold outreach</div>
|
||||
<p class="mt-1 text-[13px] text-foreground/70 leading-relaxed">Personalized prospecting to business addresses with a real reason for contact.</p>
|
||||
</div>
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-5">
|
||||
<div class="inline-flex items-center justify-center w-9 h-9 rounded-[9px] bg-emerald-50 text-emerald-700 ring-1 ring-emerald-100">
|
||||
<Icon name="flame" size={16} />
|
||||
</div>
|
||||
<div class="mt-3 text-[14.5px] font-semibold text-heading">Warmup</div>
|
||||
<p class="mt-1 text-[13px] text-foreground/70 leading-relaxed">Gradual, paced warmup traffic on pools you are entitled to use.</p>
|
||||
</div>
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-5">
|
||||
<div class="inline-flex items-center justify-center w-9 h-9 rounded-[9px] bg-amber-50 text-amber-700 ring-1 ring-amber-100">
|
||||
<Icon name="reply" size={16} />
|
||||
</div>
|
||||
<div class="mt-3 text-[14.5px] font-semibold text-heading">Reply handling</div>
|
||||
<p class="mt-1 text-[13px] text-foreground/70 leading-relaxed">Threaded follow-up on conversations you actually started.</p>
|
||||
</div>
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-5">
|
||||
<div class="inline-flex items-center justify-center w-9 h-9 rounded-[9px] bg-foreground/5 text-foreground ring-1 ring-foreground/10">
|
||||
<Icon name="inbox" size={16} />
|
||||
</div>
|
||||
<div class="mt-3 text-[14.5px] font-semibold text-heading">Transactional follow-up</div>
|
||||
<p class="mt-1 text-[13px] text-foreground/70 leading-relaxed">Operational mail to people you actually do business with.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-5 text-[14px] leading-relaxed text-foreground/70">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ===== 3. Allowed vs Not allowed (two-column visual) ===== -->
|
||||
<section id="allowed" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 3</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Allowed vs not allowed</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
A plain-English summary. The detailed rules in later sections govern in case of conflict.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 grid md:grid-cols-2 gap-4">
|
||||
<!-- Allowed -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-emerald-200/70 overflow-hidden">
|
||||
<div class="flex items-center gap-2.5 px-5 py-3 bg-emerald-50/70 border-b border-emerald-100">
|
||||
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-emerald-500 text-white">
|
||||
<Icon name="check" size={14} strokeWidth={2.5} />
|
||||
</span>
|
||||
<span class="text-[13px] font-semibold font-mono uppercase tracking-[0.12em] text-emerald-700">Allowed</span>
|
||||
</div>
|
||||
<ul class="px-5 py-4 space-y-3">
|
||||
{allowed.map((line) => (
|
||||
<li class="flex items-start gap-2.5">
|
||||
<span class="mt-2 w-1.5 h-1.5 rounded-full bg-emerald-500 shrink-0"></span>
|
||||
<span class="text-[13.5px] text-foreground/85 leading-relaxed">{line}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Not allowed -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-rose-200/70 overflow-hidden">
|
||||
<div class="flex items-center gap-2.5 px-5 py-3 bg-rose-50/70 border-b border-rose-100">
|
||||
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-rose-500 text-white">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</span>
|
||||
<span class="text-[13px] font-semibold font-mono uppercase tracking-[0.12em] text-rose-700">Not allowed</span>
|
||||
</div>
|
||||
<ul class="px-5 py-4 space-y-3">
|
||||
{notAllowed.map((line) => (
|
||||
<li class="flex items-start gap-2.5">
|
||||
<span class="mt-2 w-1.5 h-1.5 rounded-full bg-rose-500 shrink-0"></span>
|
||||
<span class="text-[13.5px] text-foreground/85 leading-relaxed">{line}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 4. Prohibited content ===== -->
|
||||
<section id="content" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 4</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Prohibited content</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-5 space-y-2.5 text-[14.5px] text-foreground/85 list-disc pl-5 marker:text-rose-400">
|
||||
<li>Content that is illegal in the sender's jurisdiction or in the recipient's.</li>
|
||||
<li>Sexual content involving minors, in any form, no exceptions.</li>
|
||||
<li>Malware, exploit code, ransomware, credential harvesters, or links to any of the above.</li>
|
||||
<li>Phishing kits or content designed to deceive a recipient into giving up credentials, payment information, or access.</li>
|
||||
<li>Targeted harassment, threats, stalking, or doxxing of individuals.</li>
|
||||
<li>Content that incites violence or hatred against people based on race, ethnicity, religion, gender, sexual orientation, disability, or other protected characteristics.</li>
|
||||
<li>Content designed to defraud the recipient, including fake invoices, fake job offers, and impersonation of trusted brands.</li>
|
||||
<li>Content that violates intellectual property rights you do not hold.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ===== 5. Prohibited senders and targeting ===== -->
|
||||
<section id="targeting" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 5</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Prohibited senders and targeting</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-5 space-y-2.5 text-[14.5px] text-foreground/85 list-disc pl-5 marker:text-rose-400">
|
||||
<li>Sending to scraped consumer lists, purchased lists, or any list where you cannot point to a per-recipient basis for contacting that specific person.</li>
|
||||
<li>Mass marketing to non-business addresses such as personal Gmail, Yahoo, Outlook, or ISP mailboxes.</li>
|
||||
<li>Dating, hookup, romance, or sextortion mail. This includes mail that pretends to be from a real or fake person seeking a relationship.</li>
|
||||
<li>MLM recruitment, crypto pump-and-dump, get-rich-quick offers, fake investment opportunities, and similar high-abuse verticals.</li>
|
||||
<li>Sending on behalf of clients in any of the above categories, even if your own brand looks legitimate.</li>
|
||||
<li>Reactivating recipients who have already opted out, asked to be removed, or marked your prior mail as spam.</li>
|
||||
<li>Splitting one campaign across many workspaces or many mailboxes to dodge per-mailbox limits.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ===== 6. Compliance with email law ===== -->
|
||||
<section id="law" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 6</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Compliance with email law</h2>
|
||||
<div class="mt-4 space-y-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 7. Authentication and identification ===== -->
|
||||
<section id="auth" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 7</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Authentication and identification</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
You must send as yourself, from infrastructure you are entitled to use, with the authentication that mailbox providers expect.
|
||||
</p>
|
||||
<ul class="mt-5 space-y-2.5 text-[14.5px] text-foreground/85 list-disc pl-5 marker:text-[color:var(--brand)]">
|
||||
<li>Real sender identity. The name, company, and address in your messages must match the person or business actually sending them.</li>
|
||||
<li>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.</li>
|
||||
<li>No impersonation of other people, brands, or organizations. No pretending mail is from someone it is not from.</li>
|
||||
<li>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.</li>
|
||||
<li>Use mailboxes you legitimately control. Connecting a mailbox you do not own, or whose owner has not authorized your use, is a hard violation.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ===== 8. Suppression and unsubscribe ===== -->
|
||||
<section id="suppression" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 8</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Suppression and unsubscribe</h2>
|
||||
<div class="mt-4 space-y-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 9. Volume and rate ===== -->
|
||||
<section id="volume" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 9</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Volume and rate</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-5 space-y-2.5 text-[14.5px] text-foreground/85 list-disc pl-5 marker:text-[color:var(--brand)]">
|
||||
<li>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.</li>
|
||||
<li>You may not bypass or trick per-mailbox daily caps and per-send spacing rules.</li>
|
||||
<li>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.</li>
|
||||
<li>You may not split a single underlying campaign across many separate workspaces to dodge our caps or our enforcement.</li>
|
||||
<li>Spinning up new mailboxes after quarantine to keep the same campaign running is treated as evasion.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ===== 10. Warmup pool integrity ===== -->
|
||||
<section id="pool" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 10</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Warmup pool integrity</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-5 space-y-2.5 text-[14.5px] text-foreground/85 list-disc pl-5 marker:text-[color:var(--brand)]">
|
||||
<li>No forging or tampering with warmup verification tokens.</li>
|
||||
<li>No synthetic engagement patterns designed to inflate scores or fake reputation.</li>
|
||||
<li>No using the shared paid pool from mailboxes that have been quarantined or are otherwise unhealthy.</li>
|
||||
<li>No mixing low-trust mailboxes into the paid pool to inflate volume.</li>
|
||||
<li>No scripted automation that imitates pool participation from outside the platform.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ===== 11. Security and integrity ===== -->
|
||||
<section id="security" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 11</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Security and integrity</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-5 space-y-2.5 text-[14.5px] text-foreground/85 list-disc pl-5 marker:text-[color:var(--brand)]">
|
||||
<li>No probing, scanning, or vulnerability testing of Warmbly infrastructure without prior written consent.</li>
|
||||
<li>No denial-of-service attempts against the API, the dashboard, the tracking edge, or any other component.</li>
|
||||
<li>No scraping of platform internals, other workspaces, other customers, or other customers' contacts.</li>
|
||||
<li>No credential stuffing, brute-forcing, or automated trial of credentials against imported mailboxes or third-party providers from Warmbly infrastructure.</li>
|
||||
<li>No interference with other customers' sending, warmup, or reply handling.</li>
|
||||
<li>No reverse-engineering of rate limits or abuse controls in order to defeat them.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ===== 12. Enforcement ===== -->
|
||||
<section id="enforcement" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 12</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Enforcement</h2>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden">
|
||||
{enforcement.map((s, i) => (
|
||||
<div class={`flex flex-col sm:flex-row sm:items-start gap-3 sm:gap-5 px-5 py-4 ${i === enforcement.length - 1 ? '' : 'border-b border-[color:var(--border)]'}`}>
|
||||
<div class="sm:w-36 shrink-0">
|
||||
<span class={`inline-flex items-center gap-1.5 h-6 px-2.5 rounded-full text-[10.5px] font-semibold font-mono uppercase tracking-[0.1em] ring-1 ${s.chip}`}>
|
||||
<span class={`w-1.5 h-1.5 rounded-full ${s.dot}`}></span>
|
||||
{s.state}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-[14.5px] font-semibold text-heading">{s.title}</div>
|
||||
<p class="mt-1 text-[13.5px] text-foreground/75 leading-relaxed">{s.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="mt-5 space-y-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 13. Reporting abuse ===== -->
|
||||
<section id="reporting" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 13</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Reporting abuse</h2>
|
||||
|
||||
<div class="mt-5 rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-5 md:p-6">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="inline-flex items-center justify-center w-10 h-10 rounded-[10px] bg-rose-50 text-rose-700 ring-1 ring-rose-100 shrink-0">
|
||||
<Icon name="warning" size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-[15px] font-semibold text-heading">Saw something? Email us.</div>
|
||||
<p class="mt-1.5 text-[13.5px] text-foreground/75 leading-relaxed">
|
||||
To report a suspected violation by a Warmbly customer, send the message and full headers to <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] font-medium" href="mailto:abuse@warmbly.com">abuse@warmbly.com</a>. We acknowledge reports within one business day and act on them based on the evidence, not based on who reported.
|
||||
</p>
|
||||
<p class="mt-2 text-[13px] text-foreground/65 leading-relaxed">
|
||||
Security issues belong at <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]" href="mailto:security@warmbly.com">security@warmbly.com</a>. Privacy questions belong at <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]" href="mailto:privacy@warmbly.com">privacy@warmbly.com</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== 14. Changes ===== -->
|
||||
<section id="changes" class="scroll-mt-20">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground mb-2">Section 14</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.02em] text-heading">Changes to this policy</h2>
|
||||
<div class="mt-4 space-y-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-[color:var(--border)] flex flex-wrap items-center justify-between gap-3 text-[12.5px] font-mono">
|
||||
<span class="text-muted-foreground">Last updated <span class="text-heading font-semibold">{lastUpdated}</span></span>
|
||||
<span class="text-muted-foreground">Questions? <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]" href="mailto:legal@warmbly.com">legal@warmbly.com</a></span>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Send cold email that earns the inbox."
|
||||
description="Real outreach, real warmup, real rules. Connect a mailbox and start the right way."
|
||||
primaryLabel="Start free trial"
|
||||
primaryHref="https://app.warmbly.com/register"
|
||||
secondaryLabel="Read the terms"
|
||||
secondaryHref="/terms/"
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
+672
-93
@@ -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' },
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="Brand · Warmbly"
|
||||
description="The Warmbly wordmark, mark, palette and typography."
|
||||
description="The Warmbly visual identity. Wordmark, mark, sky palette, typography, and usage guidance."
|
||||
>
|
||||
<!-- HERO: brand-themed pure visual + minimal text -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[440px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
<!-- ============================================================
|
||||
HERO · matches sending / developers / warmup / campaigns
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<div class="container-page relative pt-14 pb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-3">Brand</div>
|
||||
<h1 class="text-[32px] md:text-[48px] font-semibold tracking-[-0.025em] leading-[1.06] text-white max-w-3xl">
|
||||
Marks, colors, type.
|
||||
<div class="container-page relative pt-16 md:pt-24 pb-44 md:pb-56 text-center">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Brand
|
||||
</span>
|
||||
Warmbly's visual identity
|
||||
</div>
|
||||
|
||||
<h1 class="mt-8 md:mt-10 text-[44px] sm:text-6xl md:text-[72px] lg:text-[80px] font-semibold tracking-[-0.04em] leading-[0.98] text-white max-w-4xl mx-auto">
|
||||
Marks, colors,<br/>type, voice.
|
||||
</h1>
|
||||
<p class="mt-4 text-[15.5px] text-white/75 max-w-xl">
|
||||
Please do not imply partnership without permission. Otherwise these assets are free for press, announcements and integrations.
|
||||
<p class="mt-6 text-[17px] md:text-[19px] text-white/80 max-w-2xl mx-auto leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<a href="/brand.zip" class="inline-flex h-10 px-4 items-center gap-2 rounded-[10px] text-[13.5px] font-medium bg-white text-[color:var(--sky-7)] hover:bg-white/95">
|
||||
<Icon name="arrowRight" size={13} class="rotate-90" /> Download brand kit
|
||||
|
||||
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="#assets" class="inline-flex h-11 px-5 items-center gap-2 rounded-[10px] text-[14.5px] font-semibold bg-white hover:bg-white hover:-translate-y-0.5 hover:shadow-[0_12px_28px_-6px_rgba(0,0,0,0.3)] transition-all duration-200 ease-out shadow-[0_4px_14px_-2px_rgba(0,0,0,0.18)]">
|
||||
<span style="color:#075985;">Download assets</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#075985" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#palette" class="inline-flex h-11 px-5 items-center rounded-[10px] text-[14.5px] font-medium bg-white/10 backdrop-blur ring-1 ring-white/40 text-white hover:bg-white/20 hover:-translate-y-0.5 transition-all duration-200 ease-out">
|
||||
See the palette
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MARKS: 2x2 grid with caption strip -->
|
||||
<section class="py-16 md:py-20">
|
||||
<!-- ============================================================
|
||||
FLOATING SPECIMEN CARD · the mark itself, on a white card
|
||||
that pulls up over the hero like every other landing page.
|
||||
============================================================ -->
|
||||
<section class="relative -mt-36 md:-mt-44 pb-16 md:pb-24 z-10">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Marks</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Wordmark and standalone mark.</h2>
|
||||
<div class="rounded-[18px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden shadow-[0_40px_90px_-30px_rgba(2,32,71,0.35),0_12px_30px_-12px_rgba(15,23,42,0.12)]">
|
||||
|
||||
<div class="mt-10 grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
<div class="aspect-square rounded-[14px] bg-white ring-1 ring-[color:var(--border)] flex items-center justify-center" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-center gap-3">
|
||||
<Logo class="w-9 h-9 text-foreground" />
|
||||
<span class="text-[28px] font-semibold tracking-[-0.025em] text-heading">Warmbly</span>
|
||||
<!-- Topbar -->
|
||||
<div class="px-6 py-3 border-b border-[color:var(--border)] flex items-baseline justify-between gap-3">
|
||||
<div class="flex items-baseline gap-3">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground">Primary lockup</div>
|
||||
<span class="text-[11.5px] text-foreground/55">Wordmark + mark, on white</span>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center gap-2 text-[10.5px] font-mono text-muted-foreground">
|
||||
<span class="inline-flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
SVG · vector
|
||||
</span>
|
||||
<span class="text-foreground/25">·</span>
|
||||
<span>v1.0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aspect-square rounded-[14px] bg-[#0c4a6e] flex items-center justify-center">
|
||||
<div class="flex items-center gap-3 text-white">
|
||||
<Logo class="w-9 h-9 text-white" />
|
||||
<span class="text-[28px] font-semibold tracking-[-0.025em]">Warmbly</span>
|
||||
|
||||
<!-- Big lockup canvas with measurement marks -->
|
||||
<div class="relative px-6 md:px-12 py-12 md:py-20 bg-[color:var(--surface-1)]/40">
|
||||
<!-- Crosshair guides -->
|
||||
<div class="absolute inset-x-12 top-6 h-px bg-[color:var(--border)]" aria-hidden="true"></div>
|
||||
<div class="absolute inset-x-12 bottom-6 h-px bg-[color:var(--border)]" aria-hidden="true"></div>
|
||||
|
||||
<div class="relative flex items-center justify-center gap-5 md:gap-7">
|
||||
<Logo class="w-12 h-12 md:w-16 md:h-16 text-foreground" />
|
||||
<span class="text-[44px] md:text-[64px] font-semibold tracking-[-0.03em] text-heading leading-none">Warmbly</span>
|
||||
</div>
|
||||
|
||||
<!-- Caption row -->
|
||||
<div class="mt-12 flex flex-wrap items-baseline justify-center gap-x-6 gap-y-1 text-[11px] font-mono text-muted-foreground uppercase tracking-[0.14em]">
|
||||
<span>Mark height: 1x</span>
|
||||
<span class="text-foreground/25">·</span>
|
||||
<span>Wordmark cap-height: 1x</span>
|
||||
<span class="text-foreground/25">·</span>
|
||||
<span>Gap: 0.4x</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="aspect-square rounded-[14px] bg-white ring-1 ring-[color:var(--border)] flex items-center justify-center" style="box-shadow: var(--shadow-sm);">
|
||||
<Logo class="w-14 h-14 text-foreground" />
|
||||
</div>
|
||||
<div class="aspect-square rounded-[14px] bg-[#0c4a6e] flex items-center justify-center">
|
||||
<Logo class="w-14 h-14 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid sm:grid-cols-2 lg:grid-cols-4 gap-3 text-[11px] font-mono uppercase tracking-[0.14em] text-muted-foreground">
|
||||
<div>Wordmark · light</div>
|
||||
<div>Wordmark · dark</div>
|
||||
<div>Mark · light</div>
|
||||
<div>Mark · dark</div>
|
||||
<!-- Footer strip -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 divide-x divide-[color:var(--border)] border-t border-[color:var(--border)] text-[11.5px]">
|
||||
<div class="px-5 py-3">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">Min width</div>
|
||||
<div class="mt-1 font-mono text-heading">96px</div>
|
||||
</div>
|
||||
<div class="px-5 py-3">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">Mark min</div>
|
||||
<div class="mt-1 font-mono text-heading">24px</div>
|
||||
</div>
|
||||
<div class="px-5 py-3">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">Clear space</div>
|
||||
<div class="mt-1 font-mono text-heading">1x mark height</div>
|
||||
</div>
|
||||
<div class="px-5 py-3">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">Format</div>
|
||||
<div class="mt-1 font-mono text-heading">SVG · PNG</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PALETTE: long horizontal swatch strip -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-20">
|
||||
<!-- ============================================================
|
||||
MARKS · variant grid (light, dark, mono, mark-only)
|
||||
============================================================ -->
|
||||
<section id="assets" class="border-y border-[color:var(--border)] bg-white py-20 md:py-28 scroll-mt-4">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Palette</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Sky ramp. Neutral system.</h2>
|
||||
<p class="mt-3 text-[14.5px] text-foreground/70 max-w-xl">10 steps from atmosphere to deep sea. Plus a small neutral system.</p>
|
||||
<div class="grid lg:grid-cols-[1fr_1.7fr] gap-12 lg:gap-16 items-start">
|
||||
<div class="lg:sticky lg:top-24" style="align-self: start;">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Marks</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Four official lockups.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed max-w-md">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-6 space-y-2 text-[13.5px] text-foreground/80 max-w-md">
|
||||
<li class="flex items-start gap-2"><Icon name="check" size={13} class="text-[#0284c7] mt-1 shrink-0" /><span>Wordmark for sign-off, footers, partner pages.</span></li>
|
||||
<li class="flex items-start gap-2"><Icon name="check" size={13} class="text-[#0284c7] mt-1 shrink-0" /><span>Mark for favicons, avatars, app tiles, OG cards.</span></li>
|
||||
<li class="flex items-start gap-2"><Icon name="check" size={13} class="text-[#0284c7] mt-1 shrink-0" /><span>SVG everywhere it is supported. PNG at 2x for raster only.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid grid-cols-5 lg:grid-cols-10 gap-1 rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]">
|
||||
{palette.map((c) => (
|
||||
<div class="aspect-[2/3] p-2.5 flex flex-col justify-between" style={`background: ${c.hex}; color: ${c.text === 'light' ? '#fff' : '#0c4a6e'}`}>
|
||||
<div class="text-[11px] font-mono font-semibold">{c.name}</div>
|
||||
<div class="text-[10px] font-mono opacity-80 uppercase">{c.hex}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<!-- 2x2 of mark variants -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Wordmark on light -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="aspect-[5/3] flex items-center justify-center bg-white">
|
||||
<div class="flex items-center gap-3">
|
||||
<Logo class="w-8 h-8 text-foreground" />
|
||||
<span class="text-[26px] font-semibold tracking-[-0.025em] text-heading">Warmbly</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-2.5 border-t border-[color:var(--border)] flex items-center justify-between">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.14em] text-muted-foreground">Wordmark · light</div>
|
||||
<a href="/brand/warmbly-wordmark.svg" class="text-[10.5px] font-mono text-[#0369a1] hover:underline">SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ 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) => (
|
||||
<div class={`rounded-[10px] p-4 ${n.cls}`} style={`background: ${n.hex}`}>
|
||||
<div class="text-[12px] font-semibold">{n.name}</div>
|
||||
<div class="mt-1 text-[10.5px] font-mono opacity-80 uppercase">{n.hex}</div>
|
||||
<!-- Wordmark on dark -->
|
||||
<div class="rounded-[14px] ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="aspect-[5/3] flex items-center justify-center bg-[#0c4a6e]">
|
||||
<div class="flex items-center gap-3 text-white">
|
||||
<Logo class="w-8 h-8 text-white" />
|
||||
<span class="text-[26px] font-semibold tracking-[-0.025em]">Warmbly</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-2.5 border-t border-[color:var(--border)] bg-white flex items-center justify-between">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.14em] text-muted-foreground">Wordmark · dark</div>
|
||||
<a href="/brand/warmbly-wordmark-white.svg" class="text-[10.5px] font-mono text-[#0369a1] hover:underline">SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mark only · light -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="aspect-[5/3] flex items-center justify-center bg-white">
|
||||
<Logo class="w-14 h-14 text-foreground" />
|
||||
</div>
|
||||
<div class="px-4 py-2.5 border-t border-[color:var(--border)] flex items-center justify-between">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.14em] text-muted-foreground">Mark · light</div>
|
||||
<a href="/brand/warmbly-mark.svg" class="text-[10.5px] font-mono text-[#0369a1] hover:underline">SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mark only · dark -->
|
||||
<div class="rounded-[14px] ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="aspect-[5/3] flex items-center justify-center" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<Logo class="w-14 h-14 text-white" />
|
||||
</div>
|
||||
<div class="px-4 py-2.5 border-t border-[color:var(--border)] bg-white flex items-center justify-between">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.14em] text-muted-foreground">Mark · brand</div>
|
||||
<a href="/brand/warmbly-mark-white.svg" class="text-[10.5px] font-mono text-[#0369a1] hover:underline">SVG</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<!-- Size reference strip -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-4">Size reference</div>
|
||||
<div class="flex items-end gap-6 md:gap-10 flex-wrap">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Logo class="w-4 h-4 text-foreground" />
|
||||
<span class="text-[10.5px] font-mono text-muted-foreground">16px</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Logo class="w-6 h-6 text-foreground" />
|
||||
<span class="text-[10.5px] font-mono text-muted-foreground">24px</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Logo class="w-8 h-8 text-foreground" />
|
||||
<span class="text-[10.5px] font-mono text-muted-foreground">32px</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Logo class="w-12 h-12 text-foreground" />
|
||||
<span class="text-[10.5px] font-mono text-muted-foreground">48px</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Logo class="w-16 h-16 text-foreground" />
|
||||
<span class="text-[10.5px] font-mono text-muted-foreground">64px</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-5 text-[12.5px] text-foreground/65 leading-relaxed">
|
||||
The mark holds up at 16px because the angles are simple. Below 16px, use the favicon variant in <code class="font-mono text-[12px] text-[#0369a1]">/brand/favicon.ico</code> instead.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Download manifest -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="px-5 py-3 border-b border-[color:var(--border)] bg-[color:var(--surface-1)]/60 flex items-baseline justify-between">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-foreground/70">Asset manifest</div>
|
||||
<a href="/brand.zip" class="text-[11px] font-mono text-[#0369a1] hover:underline inline-flex items-center gap-1">
|
||||
Download all
|
||||
<Icon name="arrowRight" size={11} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="divide-y divide-[color:var(--border)]">
|
||||
{assets.map((a) => (
|
||||
<a href={a.href} class="flex items-center justify-between gap-3 px-5 py-2.5 hover:bg-[color:var(--surface-1)]/60 transition-colors">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<Icon name="arrowUpRight" size={12} class="text-foreground/40 shrink-0" />
|
||||
<div class="text-[12.5px] font-medium text-heading truncate">{a.label}</div>
|
||||
</div>
|
||||
<span class="text-[10px] font-mono uppercase tracking-[0.14em] text-muted-foreground shrink-0">{a.size}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- TYPE -->
|
||||
<section class="py-16 md:py-20">
|
||||
<!-- ============================================================
|
||||
PALETTE · long sky ramp + neutrals
|
||||
============================================================ -->
|
||||
<section id="palette" class="bg-[color:var(--surface-1)] border-b border-[color:var(--border)] py-20 md:py-28 scroll-mt-4">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Type</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Inter for UI. JetBrains Mono for code.</h2>
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Palette</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Sky ramp. Plus a neutral system.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 space-y-3">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-3">Display · Inter Semibold · 600 · −0.03em</div>
|
||||
<div class="text-[56px] md:text-[72px] font-semibold tracking-[-0.03em] leading-[1] text-heading">Cold email that lands.</div>
|
||||
<!-- Ramp -->
|
||||
<div class="rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)] bg-white">
|
||||
<div class="grid grid-cols-5 lg:grid-cols-10">
|
||||
{palette.map((c, i) => (
|
||||
<div
|
||||
class="relative aspect-[2/3] p-3 flex flex-col justify-between transition-transform hover:-translate-y-0.5 duration-200"
|
||||
style={`background: ${c.hex}; color: ${isLightStep[i] ? '#ffffff' : '#0c4a6e'};`}
|
||||
>
|
||||
{c.role && (
|
||||
<span class="absolute top-2 right-2 inline-flex items-center h-4 px-1.5 rounded-sm text-[9px] font-mono uppercase tracking-[0.1em] font-semibold" style={`background: ${isLightStep[i] ? 'rgba(255,255,255,0.18)' : 'rgba(12,74,110,0.12)'};`}>
|
||||
{c.role}
|
||||
</span>
|
||||
)}
|
||||
<div class="text-[11px] font-mono font-semibold">{c.name}</div>
|
||||
<div>
|
||||
<div class="text-[10px] font-mono opacity-80 uppercase">{c.hex}</div>
|
||||
<div class="mt-0.5 text-[9.5px] font-mono opacity-60">--{c.token}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-3">Heading · Inter Semibold · 600 · −0.025em</div>
|
||||
<div class="text-[28px] md:text-[36px] font-semibold tracking-[-0.025em] leading-[1.08] text-heading">A platform built so the inbox is the default outcome.</div>
|
||||
|
||||
<!-- Marker row -->
|
||||
<div class="relative border-t border-[color:var(--border)] px-3 py-3 bg-white">
|
||||
<div class="grid grid-cols-5 lg:grid-cols-10 text-[10px] font-mono text-muted-foreground">
|
||||
{palette.map((c, i) => (
|
||||
<div class="text-center">
|
||||
{i === primaryIdx && <span class="text-[#0284c7] font-semibold">Primary</span>}
|
||||
{i === hoverIdx && <span class="text-[#0369a1] font-semibold">Hover</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-3">Body · Inter Regular · 400</div>
|
||||
<p class="text-[15.5px] leading-relaxed text-foreground/85 max-w-2xl">
|
||||
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.
|
||||
</div>
|
||||
|
||||
<!-- Neutrals -->
|
||||
<div class="mt-10">
|
||||
<div class="text-[11px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-4">Neutral system</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
{neutrals.map((n) => (
|
||||
<div class={`rounded-[12px] p-4 ${n.cls}`} style={`background: ${n.hex}`}>
|
||||
<div class="text-[12px] font-semibold">{n.name}</div>
|
||||
<div class="mt-2 text-[10.5px] font-mono uppercase tracking-[0.1em] opacity-80">{n.hex}</div>
|
||||
<div class="mt-0.5 text-[10px] font-mono opacity-60">{n.sub}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Composition example -->
|
||||
<div class="mt-10 grid md:grid-cols-2 gap-4">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-4">Light surface composition</div>
|
||||
<div class="space-y-3">
|
||||
<a href="#" class="inline-flex h-9 px-4 items-center rounded-[8px] text-[13px] font-semibold text-white bg-[#0284c7] hover:bg-[#0369a1] transition-colors">
|
||||
Connect a mailbox
|
||||
</a>
|
||||
<div class="text-[13px] text-foreground">
|
||||
Healthy senders <span class="text-[#0284c7] font-medium">stay below 50/day</span>.
|
||||
</div>
|
||||
<div class="text-[11.5px] font-mono text-muted-foreground">
|
||||
warmup.pool: <span class="text-[#0369a1]">premium</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-[14px] ring-1 ring-white/10 p-6" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-white/55 mb-4">Dark surface composition</div>
|
||||
<div class="space-y-3">
|
||||
<a href="#" class="inline-flex h-9 px-4 items-center rounded-[8px] text-[13px] font-semibold bg-white" style="color:#075985;">
|
||||
Connect a mailbox
|
||||
</a>
|
||||
<div class="text-[13px] text-white/85">
|
||||
Healthy senders <span class="text-[#7dd3fc] font-medium">stay below 50/day</span>.
|
||||
</div>
|
||||
<div class="text-[11.5px] font-mono text-white/60">
|
||||
warmup.pool: <span class="text-[#7dd3fc]">premium</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
TYPOGRAPHY · live specimens at H1 / H2 / body / mono
|
||||
============================================================ -->
|
||||
<section class="border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Typography</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Inter for everything. System mono for code.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Typeface cards -->
|
||||
<div class="grid md:grid-cols-2 gap-4 mb-10">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-baseline justify-between gap-4">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Sans</div>
|
||||
<div class="text-[10.5px] font-mono text-muted-foreground">400 / 500 / 600 / 700 / 800</div>
|
||||
</div>
|
||||
<div class="mt-3 text-[44px] font-semibold tracking-[-0.03em] text-heading leading-none">Inter</div>
|
||||
<p class="mt-3 text-[13px] text-foreground/65 leading-relaxed">
|
||||
Self-hosted via <code class="font-mono text-[12px] text-[#0369a1]">@fontsource/inter</code>. Used for every UI surface, every headline, every body paragraph.
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-[14px] bg-[#0c1224] text-white/85 p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-white/55 mb-3">Mono · JetBrains Mono · 500</div>
|
||||
<pre class="text-[14px] font-mono leading-relaxed"><code>scheduler.<span style="color:#a78bfa">setWorkerCap</span>(worker.id, dailyBudget)</code></pre>
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-baseline justify-between gap-4">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Mono</div>
|
||||
<div class="text-[10.5px] font-mono text-muted-foreground">system stack</div>
|
||||
</div>
|
||||
<div class="mt-3 text-[44px] font-semibold tracking-[-0.01em] text-heading leading-none font-mono">ui-monospace</div>
|
||||
<p class="mt-3 text-[13px] text-foreground/65 leading-relaxed">
|
||||
<code class="font-mono text-[12px] text-[#0369a1]">ui-monospace, 'SF Mono', Menlo, monospace</code>. No web download. Used for code snippets, metric labels, and timestamps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Specimens -->
|
||||
<div class="space-y-3">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6 md:p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-baseline justify-between gap-4 mb-4">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Display · 72 / 64 · Semibold · -0.03em</div>
|
||||
<div class="text-[10.5px] font-mono text-muted-foreground hidden md:block">h1</div>
|
||||
</div>
|
||||
<div class="text-[52px] md:text-[72px] font-semibold tracking-[-0.03em] leading-[1] text-heading">Cold email that lands.</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6 md:p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-baseline justify-between gap-4 mb-4">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Heading · 44 / 36 · Semibold · -0.025em</div>
|
||||
<div class="text-[10.5px] font-mono text-muted-foreground hidden md:block">h2</div>
|
||||
</div>
|
||||
<div class="text-[28px] md:text-[44px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
A platform built so the inbox is the default outcome.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6 md:p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-baseline justify-between gap-4 mb-4">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Body · 15.5 · Regular · 1.7 leading</div>
|
||||
<div class="text-[10.5px] font-mono text-muted-foreground hidden md:block">p</div>
|
||||
</div>
|
||||
<p class="text-[15.5px] leading-relaxed text-foreground/85 max-w-2xl">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-[#0c1224] text-white/85 p-6 md:p-8" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-baseline justify-between gap-4 mb-4">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-white/55">Mono · 13.5 · 1.65 leading</div>
|
||||
<div class="text-[10.5px] font-mono text-white/55 hidden md:block">code</div>
|
||||
</div>
|
||||
<pre class="text-[13.5px] font-mono leading-[1.65] overflow-x-auto"><code><span style="color:#94a3b8">// Schedule a send respecting the per-mailbox cap.</span>
|
||||
scheduler.<span style="color:#a78bfa">scheduleSend</span>({
|
||||
mailbox: <span style="color:#fde68a">"ben@acme.com"</span>,
|
||||
campaign: <span style="color:#fde68a">"cmp_2025_q2"</span>,
|
||||
cap: <span style="color:#7dd3fc">50</span>, <span style="color:#94a3b8">// emails / day</span>
|
||||
gap: <span style="color:#7dd3fc">600</span>, <span style="color:#94a3b8">// seconds between sends</span>
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
VOICE PRINCIPLES · 4 short principles
|
||||
============================================================ -->
|
||||
<section class="bg-[color:var(--surface-1)] border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="grid lg:grid-cols-[1fr_2fr] gap-12 lg:gap-16 items-start">
|
||||
<div class="lg:sticky lg:top-24" style="align-self: start;">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Voice</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
How Warmbly sounds.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed max-w-md">
|
||||
Plain English. Short sentences. Numbers when they matter. If a line could come from any other SaaS site, rewrite it.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
{voice.map((v) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6 hover:-translate-y-0.5 hover:shadow-[0_18px_36px_-18px_rgba(2,32,71,0.18)] transition-all duration-200" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-[#0284c7]">{v.n}</div>
|
||||
<div class="mt-3 text-[20px] font-semibold tracking-[-0.02em] text-heading">{v.title}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/70 leading-relaxed">{v.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
DO / DON'T · side-by-side grid with visual examples
|
||||
============================================================ -->
|
||||
<section class="bg-white border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Usage</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Logo guardrails.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid lg:grid-cols-2 gap-6 lg:gap-10">
|
||||
<!-- DO -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-emerald-50 text-emerald-700 ring-1 ring-emerald-200">
|
||||
<Icon name="check" size={12} strokeWidth={2.4} />
|
||||
</span>
|
||||
<div class="text-[12px] uppercase tracking-[0.16em] font-mono text-emerald-700">Do</div>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
{dos.map((d, i) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="aspect-[5/3] flex items-center justify-center bg-[color:var(--surface-1)]/40 relative">
|
||||
{i === 0 && (
|
||||
<div class="relative">
|
||||
<div class="absolute -inset-6 border border-dashed border-emerald-300/70 rounded-md" aria-hidden="true"></div>
|
||||
<Logo class="w-12 h-12 text-foreground" />
|
||||
</div>
|
||||
)}
|
||||
{i === 1 && (
|
||||
<div class="flex items-center gap-2 text-foreground">
|
||||
<Logo class="w-7 h-7 text-foreground" />
|
||||
<span class="text-[22px] font-semibold tracking-[-0.025em] text-heading">Warmbly</span>
|
||||
</div>
|
||||
)}
|
||||
{i === 2 && (
|
||||
<div class="px-6 py-5 rounded-md" style="background:#0c4a6e;">
|
||||
<div class="flex items-center gap-2 text-white">
|
||||
<Logo class="w-7 h-7 text-white" />
|
||||
<span class="text-[22px] font-semibold tracking-[-0.025em]">Warmbly</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{i === 3 && (
|
||||
<div class="px-5 py-4 rounded-md" style="background: linear-gradient(135deg, #475569, #1e293b);">
|
||||
<Logo class="w-9 h-9 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div class="px-4 py-3 border-t border-[color:var(--border)]">
|
||||
<div class="text-[13px] font-semibold text-heading">{d.title}</div>
|
||||
<p class="mt-0.5 text-[12px] text-foreground/65 leading-relaxed">{d.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DON'T -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="inline-flex items-center justify-center w-6 h-6 rounded-full bg-rose-50 text-rose-700 ring-1 ring-rose-200">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</span>
|
||||
<div class="text-[12px] uppercase tracking-[0.16em] font-mono text-rose-700">Do not</div>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
{donts.map((d, i) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden relative" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="aspect-[5/3] flex items-center justify-center bg-[color:var(--surface-1)]/40 relative">
|
||||
<span class="absolute top-2 right-2 inline-flex items-center justify-center w-5 h-5 rounded-full bg-rose-100 text-rose-700 ring-1 ring-rose-200">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</span>
|
||||
{i === 0 && (
|
||||
<div style="transform: scaleX(1.6) scaleY(0.8);">
|
||||
<Logo class="w-12 h-12 text-foreground" />
|
||||
</div>
|
||||
)}
|
||||
{i === 1 && (
|
||||
<Logo class="w-12 h-12" style="color:#a855f7;" />
|
||||
)}
|
||||
{i === 2 && (
|
||||
<Logo class="w-12 h-12 text-foreground" style="filter: drop-shadow(0 0 12px rgba(2,132,199,0.7)) drop-shadow(0 4px 8px rgba(15,23,42,0.4));" />
|
||||
)}
|
||||
{i === 3 && (
|
||||
<div class="flex items-center gap-2">
|
||||
<Logo class="w-9 h-9 text-foreground" />
|
||||
<span class="text-foreground/45 text-[18px] font-light">×</span>
|
||||
<div class="w-9 h-9 rounded-md bg-slate-300 flex items-center justify-center text-slate-500 text-[12px] font-bold font-mono">CO</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div class="px-4 py-3 border-t border-[color:var(--border)]">
|
||||
<div class="text-[13px] font-semibold text-heading">{d.title}</div>
|
||||
<p class="mt-0.5 text-[12px] text-foreground/65 leading-relaxed">{d.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
NAMING + PRESS · final compact block
|
||||
============================================================ -->
|
||||
<section class="py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="grid md:grid-cols-2 gap-6 lg:gap-10">
|
||||
<!-- Naming -->
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-7" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Naming</div>
|
||||
<h3 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">
|
||||
Always Warmbly. One word. Capital W.
|
||||
</h3>
|
||||
<div class="mt-5 space-y-3 text-[13.5px] text-foreground/75">
|
||||
<div class="flex items-baseline gap-3">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded text-[10px] font-mono uppercase tracking-[0.1em] bg-emerald-50 text-emerald-700 ring-1 ring-emerald-200">Yes</span>
|
||||
<span class="font-medium text-heading">Warmbly</span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-3">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded text-[10px] font-mono uppercase tracking-[0.1em] bg-rose-50 text-rose-700 ring-1 ring-rose-200">No</span>
|
||||
<span class="font-mono text-foreground/55">warmbly, WARMBLY, Warm.bly, Warm Bly, WarmBly</span>
|
||||
</div>
|
||||
<p class="text-foreground/65 leading-relaxed pt-2">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Press -->
|
||||
<div class="rounded-[14px] overflow-hidden ring-1 ring-white/10 text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%); box-shadow: var(--shadow-md);">
|
||||
<div class="p-7">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-white/55 mb-3">Press & partnerships</div>
|
||||
<h3 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-white">
|
||||
Need a quote, a logo file, or an interview?
|
||||
</h3>
|
||||
<p class="mt-3 text-[13.5px] text-white/75 leading-relaxed max-w-md">
|
||||
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.
|
||||
</p>
|
||||
<div class="mt-6 flex flex-wrap items-center gap-3">
|
||||
<a href="mailto:press@warmbly.com" class="inline-flex h-10 px-4 items-center gap-2 rounded-[10px] text-[13.5px] font-semibold bg-white hover:bg-white/95" style="color:#075985;">
|
||||
<Icon name="mail" size={13} />
|
||||
press@warmbly.com
|
||||
</a>
|
||||
<a href="/brand.zip" class="inline-flex h-10 px-4 items-center gap-2 rounded-[10px] text-[13.5px] font-medium bg-white/10 backdrop-blur ring-1 ring-white/40 text-white hover:bg-white/20">
|
||||
<Icon name="arrowRight" size={13} class="rotate-90" />
|
||||
Download brand kit
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-6 pt-6 border-t border-white/15 text-[11.5px] font-mono text-white/60 leading-relaxed">
|
||||
Version 1.0 · Updated <time datetime="2026-05-27">May 27, 2026</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Build with Warmbly. Tell us if we missed an asset."
|
||||
description="The kit ships as a single zip with SVG and PNG at 2x. Open an issue if you need a format that is not in there."
|
||||
primaryLabel="Get the brand kit"
|
||||
primaryHref="/brand.zip"
|
||||
secondaryLabel="Open the repo"
|
||||
secondaryHref="https://github.com/warmbly"
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
+664
-40
@@ -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<string, string> = {
|
||||
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',
|
||||
};
|
||||
---
|
||||
<Legal
|
||||
title="Cookies"
|
||||
description="The cookies and similar technologies Warmbly uses on its websites and applications."
|
||||
effective="2026-05-01"
|
||||
<Layout
|
||||
title="Cookies · Warmbly"
|
||||
description="The cookies Warmbly sets, why we set them, and how you can control them. We do not run advertising trackers and we do not sell cookie data."
|
||||
>
|
||||
<p>
|
||||
Warmbly uses a small number of cookies. We don't run third-party advertising trackers, and we don't sell cookie data.
|
||||
</p>
|
||||
|
||||
<h2>Essential cookies</h2>
|
||||
<ul>
|
||||
<li><code>warmbly_session</code> — your authenticated session for the application. Expires when you log out or after 30 days of inactivity.</li>
|
||||
<li><code>warmbly_csrf</code> — protects against cross-site request forgery on form submissions.</li>
|
||||
<li><code>warmbly_theme</code> — remembers your light/dark theme choice.</li>
|
||||
</ul>
|
||||
<!-- =====================================================
|
||||
HERO. Shorter than product pages. Centered, with the
|
||||
last-updated date prominently displayed.
|
||||
===================================================== -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<h2>Functional cookies</h2>
|
||||
<ul>
|
||||
<li><code>warmbly_workspace</code> — last selected workspace, so you land in the right place after login.</li>
|
||||
<li><code>warmbly_dismissed_notice_*</code> — remembers which one-time notices you've dismissed.</li>
|
||||
</ul>
|
||||
<div class="container-page relative pt-16 md:pt-20 pb-28 md:pb-36 text-center">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Legal
|
||||
</span>
|
||||
Cookies policy
|
||||
</div>
|
||||
|
||||
<h2>Analytics</h2>
|
||||
<p>
|
||||
We use PostHog for product analytics (workspace-owner-disable-able). PostHog uses a first-party cookie named <code>ph_*</code> to attribute events to a session. We don't share PostHog data with third parties.
|
||||
</p>
|
||||
<h1 class="mt-8 text-[44px] sm:text-5xl md:text-[64px] lg:text-[72px] font-semibold tracking-[-0.04em] leading-[0.98] text-white max-w-3xl mx-auto">
|
||||
Cookies.
|
||||
</h1>
|
||||
|
||||
<h2>Third-party cookies</h2>
|
||||
<p>
|
||||
The Warmbly application embeds the following third-party services, each of which may set its own cookies in your browser:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Stripe</strong> — when you visit billing pages or enter card details. Required to complete payment.</li>
|
||||
<li><strong>Cloudflare Turnstile</strong> — bot-protection CAPTCHA on login, signup and password reset.</li>
|
||||
</ul>
|
||||
<p class="mt-6 text-[16px] md:text-[18px] text-white/80 max-w-2xl mx-auto leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h2>Managing cookies</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<div class="mt-8 inline-flex items-center gap-2 h-9 px-4 rounded-full bg-white/10 backdrop-blur ring-1 ring-white/30 text-white/90">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4"/><path d="M8 2v4"/><path d="M3 10h18"/>
|
||||
</svg>
|
||||
<span class="text-[12.5px] font-mono tracking-[0.04em]">Last updated {LAST_UPDATED}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>Do Not Track</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</Legal>
|
||||
<!-- =====================================================
|
||||
MAIN LAYOUT. Sticky TOC sidebar + content column.
|
||||
===================================================== -->
|
||||
<section class="relative -mt-20 md:-mt-24 pb-20 md:pb-28 z-10">
|
||||
<div class="container-page">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-[240px_minmax(0,1fr)] gap-10 lg:gap-14">
|
||||
|
||||
<!-- ============ SIDEBAR TOC ============ -->
|
||||
<aside class="lg:sticky lg:top-24 self-start">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-slate-200 p-5 shadow-[0_18px_40px_-22px_rgba(2,32,71,0.18),0_2px_6px_-2px_rgba(15,23,42,0.06)]">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">On this page</div>
|
||||
<ul class="space-y-0.5">
|
||||
{toc.map((t) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${t.id}`}
|
||||
class="block text-[13.5px] -mx-2 px-2 py-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-[color:var(--surface-2)]/60 transition-colors"
|
||||
>
|
||||
{t.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div class="mt-5 pt-5 border-t border-slate-200">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-2">Related</div>
|
||||
<ul class="space-y-1">
|
||||
<li><a class="text-[13px] text-muted-foreground hover:text-foreground" href="/privacy/">Privacy Policy</a></li>
|
||||
<li><a class="text-[13px] text-muted-foreground hover:text-foreground" href="/dpa/">Data Processing Addendum</a></li>
|
||||
<li><a class="text-[13px] text-muted-foreground hover:text-foreground" href="/subprocessors/">Subprocessors</a></li>
|
||||
<li><a class="text-[13px] text-muted-foreground hover:text-foreground" href="/terms/">Terms of Service</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="#cookie-settings"
|
||||
class="mt-3 group inline-flex w-full items-center justify-between gap-2 h-11 px-4 rounded-[10px] bg-[color:var(--brand)] hover:bg-[color:var(--brand-strong)] text-white text-[13.5px] font-semibold transition-colors"
|
||||
>
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
|
||||
</svg>
|
||||
Manage preferences
|
||||
</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="transition-transform group-hover:translate-x-0.5" aria-hidden="true">
|
||||
<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
</aside>
|
||||
|
||||
<!-- ============ MAIN CONTENT ============ -->
|
||||
<article class="min-w-0">
|
||||
|
||||
<!-- WHAT COOKIES ARE -->
|
||||
<section id="what-cookies-are" class="scroll-mt-24">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Background</div>
|
||||
<h2 class="text-[26px] md:text-[32px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
What cookies are.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/85 leading-relaxed max-w-3xl">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- HOW WE USE -->
|
||||
<section id="how-we-use" class="scroll-mt-24 mt-14">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Overview</div>
|
||||
<h2 class="text-[26px] md:text-[32px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
How Warmbly uses cookies.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/85 leading-relaxed max-w-3xl">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 rounded-[16px] bg-white ring-1 ring-slate-200 overflow-hidden shadow-[0_18px_40px_-22px_rgba(2,32,71,0.18),0_2px_6px_-2px_rgba(15,23,42,0.06)]">
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-4">
|
||||
{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 (
|
||||
<div class={`p-5 md:p-6 border-slate-200 ${borderRight} ${borderRightLg} ${borderBottom} ${borderBottomSm}`}>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] text-muted-foreground font-mono">{o.k}</div>
|
||||
<div class="mt-2 text-[34px] font-semibold tracking-[-0.03em] text-heading leading-none">{o.v}</div>
|
||||
<div class="mt-2 text-[12.5px] text-muted-foreground leading-relaxed">{o.note}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CATEGORY SECTIONS WITH TABLES -->
|
||||
{categories.map((cat) => (
|
||||
<section id={cat.id} class="scroll-mt-24 mt-16">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class={`inline-flex items-center h-5 px-2 rounded text-[10.5px] font-semibold uppercase tracking-[0.08em] ${toneStyles[cat.tone]}`}>
|
||||
{cat.eyebrow}
|
||||
</span>
|
||||
<span class="text-[11px] font-mono text-muted-foreground">{cat.rows.length} {cat.rows.length === 1 ? 'cookie' : 'cookies'}</span>
|
||||
</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
{cat.title}
|
||||
</h2>
|
||||
<p class="mt-3 text-[14.5px] text-foreground/80 leading-relaxed max-w-3xl">{cat.intro}</p>
|
||||
|
||||
{cat.rows.length === 0 ? (
|
||||
<div class="mt-5 rounded-[14px] border border-dashed border-slate-300 bg-[color:var(--surface-2)]/60 px-5 py-6 flex items-start gap-3">
|
||||
<div class="mt-0.5 w-7 h-7 rounded-full bg-white ring-1 ring-slate-200 flex items-center justify-center text-slate-400">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="10"/><path d="M8 12h8"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-[14px] font-medium text-heading">{cat.emptyNote}</div>
|
||||
<div class="mt-1 text-[12.5px] text-muted-foreground">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.</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<!-- Desktop table -->
|
||||
<div class="mt-5 hidden md:block rounded-[14px] bg-white ring-1 ring-slate-200 overflow-hidden shadow-[0_18px_40px_-22px_rgba(2,32,71,0.16),0_2px_6px_-2px_rgba(15,23,42,0.05)]">
|
||||
<table class="w-full text-[13.5px]">
|
||||
<thead class="bg-[color:var(--surface-2)]/70 text-[11px] uppercase tracking-[0.12em] text-muted-foreground">
|
||||
<tr>
|
||||
<th class="text-left font-semibold px-5 py-3">Name</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Provider</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Purpose</th>
|
||||
<th class="text-left font-semibold px-5 py-3 whitespace-nowrap">Duration</th>
|
||||
<th class="text-left font-semibold px-5 py-3 whitespace-nowrap">Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
{cat.rows.map((c) => (
|
||||
<tr class="align-top hover:bg-[color:var(--surface-2)]/40 transition-colors">
|
||||
<td class="px-5 py-3.5">
|
||||
<code class="font-mono text-[12.5px] px-1.5 py-0.5 rounded bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)] text-foreground">{c.name}</code>
|
||||
{c.essential && (
|
||||
<div class="mt-1.5 inline-flex items-center gap-1 text-[10.5px] font-medium text-rose-700">
|
||||
<span class="w-1 h-1 rounded-full bg-rose-500"></span>
|
||||
Essential
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td class="px-5 py-3.5 text-foreground/85 whitespace-nowrap">{c.provider}</td>
|
||||
<td class="px-5 py-3.5 text-foreground/75 leading-relaxed">{c.purpose}</td>
|
||||
<td class="px-5 py-3.5 text-muted-foreground whitespace-nowrap">{c.duration}</td>
|
||||
<td class="px-5 py-3.5 whitespace-nowrap">
|
||||
<span class={`inline-flex items-center h-5 px-1.5 rounded text-[10.5px] font-medium ${c.party === '1st' ? 'bg-sky-50 text-sky-700' : 'bg-slate-100 text-slate-700'}`}>
|
||||
{c.party === '1st' ? '1st party' : '3rd party'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile stacked cards -->
|
||||
<div class="mt-5 md:hidden space-y-3">
|
||||
{cat.rows.map((c) => (
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-slate-200 p-4 shadow-[0_8px_22px_-16px_rgba(2,32,71,0.18),0_1px_3px_-1px_rgba(15,23,42,0.05)]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<code class="font-mono text-[12.5px] px-1.5 py-0.5 rounded bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)] text-foreground break-all">{c.name}</code>
|
||||
<span class={`shrink-0 inline-flex items-center h-5 px-1.5 rounded text-[10.5px] font-medium ${c.party === '1st' ? 'bg-sky-50 text-sky-700' : 'bg-slate-100 text-slate-700'}`}>
|
||||
{c.party === '1st' ? '1st party' : '3rd party'}
|
||||
</span>
|
||||
</div>
|
||||
{c.essential && (
|
||||
<div class="mt-2 inline-flex items-center gap-1 text-[10.5px] font-medium text-rose-700">
|
||||
<span class="w-1 h-1 rounded-full bg-rose-500"></span>
|
||||
Essential
|
||||
</div>
|
||||
)}
|
||||
<p class="mt-2 text-[13px] text-foreground/80 leading-relaxed">{c.purpose}</p>
|
||||
<dl class="mt-3 grid grid-cols-2 gap-2 text-[11.5px]">
|
||||
<div>
|
||||
<dt class="uppercase tracking-[0.12em] text-muted-foreground font-mono text-[10px]">Provider</dt>
|
||||
<dd class="mt-0.5 text-foreground/85">{c.provider}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="uppercase tracking-[0.12em] text-muted-foreground font-mono text-[10px]">Duration</dt>
|
||||
<dd class="mt-0.5 text-foreground/85">{c.duration}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
|
||||
<!-- THIRD-PARTY COOKIES SECTION -->
|
||||
<section id="third-party" class="scroll-mt-24 mt-16">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="inline-flex items-center h-5 px-2 rounded text-[10.5px] font-semibold uppercase tracking-[0.08em] bg-slate-100 text-slate-700 ring-1 ring-slate-200">
|
||||
Vendors
|
||||
</span>
|
||||
<span class="text-[11px] font-mono text-muted-foreground">{thirdParty.length} cookies</span>
|
||||
</div>
|
||||
<h2 class="text-[24px] md:text-[28px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
Third-party cookies.
|
||||
</h2>
|
||||
<p class="mt-3 text-[14.5px] text-foreground/80 leading-relaxed max-w-3xl">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-5 grid sm:grid-cols-2 gap-3">
|
||||
<a href="https://stripe.com/legal/cookies-policy" target="_blank" rel="noopener noreferrer" class="group rounded-[14px] bg-white ring-1 ring-slate-200 p-5 hover:ring-[color:var(--brand)] hover:-translate-y-0.5 transition-all duration-200 shadow-[0_18px_40px_-22px_rgba(2,32,71,0.18),0_2px_6px_-2px_rgba(15,23,42,0.06)]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[14px] font-semibold text-heading">Stripe</div>
|
||||
<Icon name="arrowUpRight" size={14} class="text-muted-foreground group-hover:text-[color:var(--brand)] transition-colors" />
|
||||
</div>
|
||||
<p class="mt-2 text-[12.5px] text-muted-foreground leading-relaxed">Sets cookies only when you reach a billing or checkout page. Used for fraud prevention and to keep checkout state.</p>
|
||||
<div class="mt-3 text-[10.5px] uppercase tracking-[0.12em] text-muted-foreground font-mono">stripe.com/legal/cookies-policy</div>
|
||||
</a>
|
||||
<a href="https://www.cloudflare.com/cookie-policy/" target="_blank" rel="noopener noreferrer" class="group rounded-[14px] bg-white ring-1 ring-slate-200 p-5 hover:ring-[color:var(--brand)] hover:-translate-y-0.5 transition-all duration-200 shadow-[0_18px_40px_-22px_rgba(2,32,71,0.18),0_2px_6px_-2px_rgba(15,23,42,0.06)]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[14px] font-semibold text-heading">Cloudflare</div>
|
||||
<Icon name="arrowUpRight" size={14} class="text-muted-foreground group-hover:text-[color:var(--brand)] transition-colors" />
|
||||
</div>
|
||||
<p class="mt-2 text-[12.5px] text-muted-foreground leading-relaxed">Provides DDoS protection, bot management, and Turnstile CAPTCHA on auth surfaces. Necessary for the app to load safely.</p>
|
||||
<div class="mt-3 text-[10.5px] uppercase tracking-[0.12em] text-muted-foreground font-mono">cloudflare.com/cookie-policy</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 hidden md:block rounded-[14px] bg-white ring-1 ring-slate-200 overflow-hidden shadow-[0_18px_40px_-22px_rgba(2,32,71,0.16),0_2px_6px_-2px_rgba(15,23,42,0.05)]">
|
||||
<table class="w-full text-[13.5px]">
|
||||
<thead class="bg-[color:var(--surface-2)]/70 text-[11px] uppercase tracking-[0.12em] text-muted-foreground">
|
||||
<tr>
|
||||
<th class="text-left font-semibold px-5 py-3">Name</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Provider</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Purpose</th>
|
||||
<th class="text-left font-semibold px-5 py-3 whitespace-nowrap">Duration</th>
|
||||
<th class="text-left font-semibold px-5 py-3 whitespace-nowrap">Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
{thirdParty.map((c) => (
|
||||
<tr class="align-top hover:bg-[color:var(--surface-2)]/40 transition-colors">
|
||||
<td class="px-5 py-3.5">
|
||||
<code class="font-mono text-[12.5px] px-1.5 py-0.5 rounded bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)] text-foreground">{c.name}</code>
|
||||
</td>
|
||||
<td class="px-5 py-3.5 text-foreground/85 whitespace-nowrap">{c.provider}</td>
|
||||
<td class="px-5 py-3.5 text-foreground/75 leading-relaxed">{c.purpose}</td>
|
||||
<td class="px-5 py-3.5 text-muted-foreground whitespace-nowrap">{c.duration}</td>
|
||||
<td class="px-5 py-3.5 whitespace-nowrap">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded text-[10.5px] font-medium bg-slate-100 text-slate-700">3rd party</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 md:hidden space-y-3">
|
||||
{thirdParty.map((c) => (
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-slate-200 p-4 shadow-[0_8px_22px_-16px_rgba(2,32,71,0.18),0_1px_3px_-1px_rgba(15,23,42,0.05)]">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<code class="font-mono text-[12.5px] px-1.5 py-0.5 rounded bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)] text-foreground break-all">{c.name}</code>
|
||||
<span class="shrink-0 inline-flex items-center h-5 px-1.5 rounded text-[10.5px] font-medium bg-slate-100 text-slate-700">3rd party</span>
|
||||
</div>
|
||||
<p class="mt-2 text-[13px] text-foreground/80 leading-relaxed">{c.purpose}</p>
|
||||
<dl class="mt-3 grid grid-cols-2 gap-2 text-[11.5px]">
|
||||
<div>
|
||||
<dt class="uppercase tracking-[0.12em] text-muted-foreground font-mono text-[10px]">Provider</dt>
|
||||
<dd class="mt-0.5 text-foreground/85">{c.provider}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="uppercase tracking-[0.12em] text-muted-foreground font-mono text-[10px]">Duration</dt>
|
||||
<dd class="mt-0.5 text-foreground/85">{c.duration}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- YOUR CHOICES -->
|
||||
<section id="choices" class="scroll-mt-24 mt-16">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Controls</div>
|
||||
<h2 class="text-[26px] md:text-[32px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
Your choices and controls.
|
||||
</h2>
|
||||
<p class="mt-3 text-[14.5px] text-foreground/80 leading-relaxed max-w-3xl">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 grid sm:grid-cols-2 gap-3">
|
||||
{controls.map((c) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-slate-200 p-5 shadow-[0_18px_40px_-22px_rgba(2,32,71,0.16),0_2px_6px_-2px_rgba(15,23,42,0.05)]">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="w-8 h-8 rounded-[8px] bg-[color:var(--brand-soft)] ring-1 ring-sky-100 flex items-center justify-center text-[color:var(--brand-strong)]">
|
||||
<Icon name={c.icon} size={15} />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-heading tracking-[-0.01em]">{c.title}</div>
|
||||
</div>
|
||||
<p class="mt-3 text-[13px] text-foreground/75 leading-relaxed">{c.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Manage preferences card -->
|
||||
<div id="cookie-settings" class="scroll-mt-24 mt-8 rounded-[16px] overflow-hidden ring-1 ring-slate-200 bg-gradient-to-br from-sky-50 via-white to-white shadow-[0_30px_60px_-30px_rgba(2,32,71,0.25)]">
|
||||
<div class="p-6 md:p-8 grid md:grid-cols-[1fr_auto] items-center gap-5">
|
||||
<div>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] text-[color:var(--brand-strong)] font-mono">Manage your preferences</div>
|
||||
<h3 class="mt-2 text-[20px] md:text-[22px] font-semibold tracking-[-0.02em] text-heading">Open the cookie settings panel.</h3>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/75 leading-relaxed max-w-xl">
|
||||
Adjust optional cookies, view what is currently stored in your browser, and reset preferences. Strictly necessary cookies cannot be disabled without signing you out.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="#cookie-settings"
|
||||
class="inline-flex items-center justify-center gap-2 h-11 px-5 rounded-[10px] bg-[color:var(--brand)] hover:bg-[color:var(--brand-strong)] text-white text-[13.5px] font-semibold transition-colors"
|
||||
>
|
||||
Open settings
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CHANGES -->
|
||||
<section id="changes" class="scroll-mt-24 mt-16">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Maintenance</div>
|
||||
<h2 class="text-[26px] md:text-[32px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
Changes to this policy.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/85 leading-relaxed max-w-3xl">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- CONTACT -->
|
||||
<section id="contact" class="scroll-mt-24 mt-16">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Contact</div>
|
||||
<h2 class="text-[26px] md:text-[32px] font-semibold tracking-[-0.025em] leading-[1.1] text-heading">
|
||||
Contact.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/85 leading-relaxed max-w-3xl">
|
||||
Questions about this policy or a specific cookie set by Warmbly? Email
|
||||
<a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] font-medium" href="mailto:privacy@warmbly.com">privacy@warmbly.com</a>
|
||||
and we will respond within 30 days. For a copy of our Data Processing Addendum, see
|
||||
<a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] font-medium" href="/dpa/">/dpa</a>.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 pt-6 border-t border-slate-200 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-[12.5px] text-muted-foreground">
|
||||
<div>Warmbly Labs, Inc. 1209 N. Orange Street, Wilmington, DE 19801, USA.</div>
|
||||
<div class="font-mono">Last updated {LAST_UPDATED}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
@@ -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' },
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="Deliverability · Warmbly"
|
||||
description="A platform built around inbox placement: per-mailbox health, real-time complaint and bounce signals, automatic quarantine before providers act."
|
||||
description="Mailbox-first deliverability. Per-mailbox caps, signed warmup tokens, pool isolation, and auto-quarantine bands that act weeks before providers do."
|
||||
>
|
||||
<!-- HERO -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[420px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
<!-- ============================================================
|
||||
HERO · HeroAtmosphere, matches sending / warmup / developers
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<div class="container-page relative pt-16 md:pt-20 pb-16 md:pb-20">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-4">Deliverability</div>
|
||||
<h1 class="text-[36px] sm:text-[52px] md:text-[60px] font-semibold tracking-[-0.03em] leading-[1.02] text-white max-w-3xl">
|
||||
Built so the inbox is the default outcome.
|
||||
<div class="container-page relative pt-16 md:pt-24 pb-44 md:pb-56 text-center">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Deliverability
|
||||
</span>
|
||||
Mailbox-first, not worker-first
|
||||
</div>
|
||||
|
||||
<h1 class="mt-8 md:mt-10 text-[44px] sm:text-6xl md:text-[72px] lg:text-[80px] font-semibold tracking-[-0.04em] leading-[0.98] text-white max-w-4xl mx-auto">
|
||||
The inbox is<br/>the only outcome that counts.
|
||||
</h1>
|
||||
<p class="mt-5 text-[15.5px] md:text-[17px] text-white/75 max-w-2xl leading-relaxed">
|
||||
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.
|
||||
<p class="mt-6 text-[17px] md:text-[19px] text-white/80 max-w-2xl mx-auto leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="https://app.warmbly.com/register" class="inline-flex h-11 px-5 items-center gap-2 rounded-[10px] text-[14.5px] font-semibold bg-white hover:bg-white hover:-translate-y-0.5 hover:shadow-[0_12px_28px_-6px_rgba(0,0,0,0.3)] transition-all duration-200 ease-out shadow-[0_4px_14px_-2px_rgba(0,0,0,0.18)]">
|
||||
<span style="color:#075985;">Audit a mailbox</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#075985" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#stance" class="inline-flex h-11 px-5 items-center rounded-[10px] text-[14.5px] font-medium bg-white/10 backdrop-blur ring-1 ring-white/40 text-white hover:bg-white/20 hover:-translate-y-0.5 transition-all duration-200 ease-out">
|
||||
Read the stance
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- AUTH STRIP -->
|
||||
<section class="py-16 md:py-20">
|
||||
<!-- ============================================================
|
||||
FLOATING DASHBOARD · per-mailbox health roster
|
||||
============================================================ -->
|
||||
<section class="relative -mt-36 md:-mt-44 pb-16 md:pb-24 z-10">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Authentication</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">SPF, DKIM, DMARC. Checked, not assumed.</h2>
|
||||
<div class="rounded-[18px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden shadow-[0_40px_90px_-30px_rgba(2,32,71,0.35),0_12px_30px_-12px_rgba(15,23,42,0.12)]">
|
||||
|
||||
<div class="mt-10 grid md:grid-cols-3 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
<!-- Topbar -->
|
||||
<div class="px-6 py-3 border-b border-[color:var(--border)] flex items-baseline justify-between gap-3">
|
||||
<div class="flex items-baseline gap-3">
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.18em] text-muted-foreground">Mailbox health</div>
|
||||
<span class="text-[11.5px] text-foreground/55">Per-mailbox state, rolling 7-day window</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center h-6 px-2 rounded-md text-[10.5px] font-mono text-[#0369a1] bg-[color:var(--sky-1)]">live</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- State legend strip -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 divide-x divide-[color:var(--border)] border-b border-[color:var(--border)]">
|
||||
{[
|
||||
{ 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) => (
|
||||
<div class="px-5 py-3.5 flex items-baseline justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={`w-2 h-2 rounded-full ${s.c}`}></span>
|
||||
<span class="text-[11.5px] uppercase tracking-[0.14em] font-mono text-foreground/70">{s.l}</span>
|
||||
</div>
|
||||
<span class="text-[14.5px] font-mono font-semibold text-heading tabular-nums">{s.n}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Table header -->
|
||||
<div class="hidden md:grid grid-cols-[minmax(0,1.6fr)_90px_70px_70px_70px_90px_80px_70px] gap-3 px-6 py-2.5 bg-[color:var(--surface-1)]/60 border-b border-[color:var(--border)] text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">
|
||||
<div>Mailbox</div>
|
||||
<div>State</div>
|
||||
<div class="text-right">Spam</div>
|
||||
<div class="text-right">Complaint</div>
|
||||
<div class="text-right">Bounce</div>
|
||||
<div class="text-right">Cap</div>
|
||||
<div class="text-right">Sent / d</div>
|
||||
<div class="text-right">Gap</div>
|
||||
</div>
|
||||
|
||||
<!-- Mailbox rows -->
|
||||
<div class="divide-y divide-[color:var(--border)]">
|
||||
{mailboxes.map((m) => {
|
||||
const t = stateTone(m.state);
|
||||
return (
|
||||
<div class="grid grid-cols-[1fr_90px] md:grid-cols-[minmax(0,1.6fr)_90px_70px_70px_70px_90px_80px_70px] gap-3 items-center px-6 py-3 hover:bg-[color:var(--surface-1)]/60 transition-colors">
|
||||
<div class="min-w-0 flex items-center gap-3">
|
||||
<Icon name="mail" size={13} class="text-foreground/45 shrink-0" />
|
||||
<div class="min-w-0">
|
||||
<div class="text-[12.5px] font-medium text-heading truncate">{m.address}</div>
|
||||
<div class="md:hidden mt-0.5 text-[10.5px] font-mono text-muted-foreground">spam {m.spam} · comp {m.complaint} · bounce {m.bounce}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class={`inline-flex items-center gap-1 h-5 px-1.5 rounded text-[10px] uppercase tracking-[0.08em] font-medium ${t.chip}`}>
|
||||
<span class={`w-1 h-1 rounded-full ${t.dot}`}></span>
|
||||
{m.state}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hidden md:block text-right font-mono text-[11.5px] text-foreground/70 tabular-nums">{m.spam}</div>
|
||||
<div class="hidden md:block text-right font-mono text-[11.5px] text-foreground/70 tabular-nums">{m.complaint}</div>
|
||||
<div class="hidden md:block text-right font-mono text-[11.5px] text-foreground/70 tabular-nums">{m.bounce}</div>
|
||||
<div class="hidden md:block text-right font-mono text-[11px] text-foreground/65 tabular-nums">{m.cap}</div>
|
||||
<div class="hidden md:block text-right font-mono text-[11.5px] text-heading tabular-nums">{m.sent}</div>
|
||||
<div class="hidden md:block text-right font-mono text-[11px] text-muted-foreground tabular-nums">{m.gap}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<!-- Footer note -->
|
||||
<div class="px-6 py-3 border-t border-[color:var(--border)] bg-[color:var(--surface-1)]/60 flex items-center justify-between text-[11px] font-mono text-muted-foreground">
|
||||
<span>7 mailboxes · 5 states</span>
|
||||
<span class="inline-flex items-center gap-1.5 text-foreground/55">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
evaluated every event
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 text-center text-[11.5px] font-mono text-muted-foreground">
|
||||
app.warmbly.com/mailboxes
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
STANCE · the opinionated critique
|
||||
============================================================ -->
|
||||
<section id="stance" class="border-y border-[color:var(--border)] bg-[color:var(--surface-1)]/40 py-20 md:py-28 scroll-mt-4">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Stance</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Why most warmup tools harm you.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 lg:gap-5">
|
||||
{critique.map((c, i) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6 md:p-7">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] text-[#0284c7] font-semibold tabular-nums">{String(i + 1).padStart(2, '0')}</span>
|
||||
<div class="text-[16px] md:text-[17px] font-semibold tracking-[-0.015em] text-heading leading-snug">{c.h}</div>
|
||||
</div>
|
||||
<p class="text-[13.5px] text-foreground/70 leading-relaxed">{c.b}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
MAILBOX-FIRST MODEL · diagram + caption
|
||||
============================================================ -->
|
||||
<section class="border-b border-[color:var(--border)] py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Mailbox-first model</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Every limit lives on a mailbox.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-[#0c1224] ring-1 ring-white/10 overflow-hidden">
|
||||
<div class="px-6 py-3 border-b border-white/10 flex items-center justify-between text-[10.5px] font-mono text-white/55">
|
||||
<span>mailbox model · v2</span>
|
||||
<span>mailbox is the unit of reputation, pacing, and policy</span>
|
||||
</div>
|
||||
|
||||
<div class="p-6 md:p-10">
|
||||
<svg viewBox="0 0 1000 380" class="w-full h-auto">
|
||||
<defs>
|
||||
<linearGradient id="mbxLine" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#7dd3fc" stop-opacity="0.7"/>
|
||||
<stop offset="100%" stop-color="#7dd3fc" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Lane labels -->
|
||||
<text x="100" y="24" fill="#7dd3fc" font-size="10" font-family="ui-monospace,monospace" letter-spacing="2">PER-MAILBOX POLICY</text>
|
||||
<text x="430" y="24" fill="#7dd3fc" font-size="10" font-family="ui-monospace,monospace" letter-spacing="2">MAILBOX</text>
|
||||
<text x="700" y="24" fill="#7dd3fc" font-size="10" font-family="ui-monospace,monospace" letter-spacing="2">EXECUTION SURFACE</text>
|
||||
|
||||
<!-- LEFT column · policies -->
|
||||
{[
|
||||
{ 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) => (
|
||||
<g>
|
||||
<rect x="40" y={p.y} width="240" height="44" rx="8" fill="rgba(255,255,255,0.05)" stroke="#7dd3fc" stroke-opacity="0.35"/>
|
||||
<text x="55" y={p.y + 18} fill="white" font-size="11.5" font-weight="600" font-family="ui-monospace,monospace">{p.label}</text>
|
||||
<text x="55" y={p.y + 33} fill="#cbd5e1" font-size="10" font-family="ui-monospace,monospace">{p.value}</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
<!-- CENTER · mailbox node -->
|
||||
<rect x="400" y="110" width="200" height="160" rx="14" fill="rgba(2,132,199,0.18)" stroke="#0ea5e9" stroke-opacity="0.75" stroke-width="1.5"/>
|
||||
<circle cx="500" cy="155" r="22" fill="rgba(125,211,252,0.18)" stroke="#7dd3fc" stroke-opacity="0.85" stroke-width="1.2"/>
|
||||
<text x="500" y="159" text-anchor="middle" fill="white" font-size="11" font-family="ui-monospace,monospace">mbx</text>
|
||||
<text x="500" y="200" text-anchor="middle" fill="white" font-size="13" font-weight="600">ben@acme.com</text>
|
||||
<text x="500" y="218" text-anchor="middle" fill="#7dd3fc" font-size="10" font-family="ui-monospace,monospace">healthy</text>
|
||||
<text x="500" y="250" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="ui-monospace,monospace">47 / 50 sent · gap 11m</text>
|
||||
|
||||
<!-- RIGHT column · execution -->
|
||||
{[
|
||||
{ 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) => (
|
||||
<g>
|
||||
<rect x="720" y={p.y} width="240" height="44" rx="8" fill="rgba(255,255,255,0.05)" stroke="#7dd3fc" stroke-opacity="0.35"/>
|
||||
<text x="735" y={p.y + 18} fill="white" font-size="11.5" font-weight="600" font-family="ui-monospace,monospace">{p.label}</text>
|
||||
<text x="735" y={p.y + 33} fill="#cbd5e1" font-size="10" font-family="ui-monospace,monospace">{p.value}</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
<!-- Connecting lines · left to mailbox -->
|
||||
{[72, 132, 192, 252, 312].map((y) => (
|
||||
<line x1="280" y1={y} x2="400" y2="190" stroke="url(#mbxLine)" stroke-width="1.5"/>
|
||||
))}
|
||||
|
||||
<!-- Connecting lines · mailbox to right -->
|
||||
{[92, 152, 212, 272].map((y) => (
|
||||
<line x1="600" y1="190" x2="720" y2={y} stroke="url(#mbxLine)" stroke-width="1.5"/>
|
||||
))}
|
||||
|
||||
<!-- Animated send marker -->
|
||||
<circle r="3" fill="#7dd3fc" opacity="0.95">
|
||||
<animateMotion dur="3s" repeatCount="indefinite" path="M 600 190 L 720 130"/>
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid sm:grid-cols-2 lg:grid-cols-3 gap-4 text-[12.5px]">
|
||||
{[
|
||||
{ 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) => (
|
||||
<div class="flex items-baseline gap-3">
|
||||
<span class="w-2 h-2 rounded-full mt-1.5 shrink-0" style={`background: ${l.c}`}></span>
|
||||
<div>
|
||||
<div class="text-foreground font-semibold">{l.t}</div>
|
||||
<div class="text-foreground/60">{l.d}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
DEFAULTS · spec sheet, real numbers
|
||||
============================================================ -->
|
||||
<section class="bg-white py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Defaults</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
The numbers we ship with.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-x-12 gap-y-px">
|
||||
{defaults.map((d, i) => (
|
||||
<div class="grid grid-cols-[1fr_auto] gap-6 py-6 border-b border-[color:var(--border)] items-baseline hover:bg-[color:var(--surface-1)]/40 -mx-3 px-3 rounded-[6px] transition-colors">
|
||||
<div>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.22em] font-mono text-muted-foreground">
|
||||
{String(i + 1).padStart(2, '0')} · {d.k}
|
||||
</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/65 leading-relaxed max-w-md">{d.why}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading font-mono whitespace-nowrap">{d.v}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-[11.5px] font-mono text-muted-foreground">
|
||||
Source: <span class="text-foreground/75">internal/config/constants.go</span> · <span class="text-foreground/75">internal/scheduler/email_scheduler.go</span> · <span class="text-foreground/75">internal/repository/pg_warmup.go</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
POOL ISOLATION · 4 surfaces
|
||||
============================================================ -->
|
||||
<section class="border-y border-[color:var(--border)] bg-[color:var(--surface-1)]/40 py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Pool isolation</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Four pools. Zero crossover.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4 lg:gap-5">
|
||||
{pools.map((p) => (
|
||||
<div class={`relative rounded-[14px] bg-white overflow-hidden ${p.featured ? 'ring-2 ring-[#0284c7]/35 shadow-[0_20px_40px_-20px_rgba(2,132,199,0.3)]' : 'ring-1 ring-[color:var(--border)]'}`}>
|
||||
<div class="h-1.5" style={`background:${p.accent}`}></div>
|
||||
{p.featured && (
|
||||
<div class="absolute top-1.5 right-4 inline-flex items-center h-5 px-2 rounded-b text-[10px] font-semibold uppercase tracking-[0.14em] font-mono bg-[#0284c7] text-white">
|
||||
Most paid plans
|
||||
</div>
|
||||
)}
|
||||
<div class="p-6">
|
||||
<div class="flex items-baseline justify-between mb-1">
|
||||
<div class={`text-[18px] font-semibold tracking-[-0.02em] ${p.featured ? 'text-[#0369a1]' : 'text-heading'}`}>{p.name}</div>
|
||||
<div class="text-[10.5px] font-mono uppercase tracking-[0.16em] text-muted-foreground">{p.when}</div>
|
||||
</div>
|
||||
<p class="mt-1 text-[13px] text-foreground/70 leading-snug">{p.summary}</p>
|
||||
<ul class="mt-4 space-y-2 text-[13px] text-foreground/75">
|
||||
{p.rules.map((r) => (
|
||||
<li class="flex items-start gap-2">
|
||||
<Icon name="check" size={12} class={p.featured ? 'text-[#0284c7] mt-1 shrink-0' : 'text-foreground/55 mt-1 shrink-0'} />
|
||||
<span>{r}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
HEALTH BANDS · auto-quarantine gauge + table
|
||||
============================================================ -->
|
||||
<section class="py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Auto-quarantine bands</div>
|
||||
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Bands that act before providers do.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-7 md:p-10">
|
||||
<div class="flex items-baseline justify-between mb-7">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.22em] font-mono text-muted-foreground">Spam placement · 0 to 100%</div>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.22em] font-mono text-muted-foreground">Per-mailbox rolling window</div>
|
||||
</div>
|
||||
|
||||
<!-- The gauge bar with 4 visible bands plus catastrophic gradient -->
|
||||
<div class="relative h-12 rounded-[6px] overflow-hidden flex">
|
||||
<div class="flex-[10] bg-emerald-100 relative">
|
||||
<div class="absolute inset-0 flex items-center px-3">
|
||||
<span class="text-[11px] font-mono font-semibold text-emerald-700">HEALTHY · 0 to 10%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-[10] bg-amber-100 relative">
|
||||
<div class="absolute inset-0 flex items-center px-3">
|
||||
<span class="text-[11px] font-mono font-semibold text-amber-700">WATCH · 10 to 20%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-[20] bg-rose-100 relative">
|
||||
<div class="absolute inset-0 flex items-center px-3">
|
||||
<span class="text-[11px] font-mono font-semibold text-rose-700">QUARANTINED · 20 to 40%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-[40] relative" style="background: linear-gradient(90deg, #fda4af 0%, #9f1239 100%);">
|
||||
<div class="absolute inset-0 flex items-center px-3">
|
||||
<span class="text-[11px] font-mono font-semibold text-white">BLOCKED · 40% to 80%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-[20] relative" style="background: linear-gradient(90deg, #881337 0%, #450a0a 100%);">
|
||||
<div class="absolute inset-0 flex items-center px-3">
|
||||
<span class="text-[11px] font-mono font-semibold text-white">CATASTROPHIC</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tick marks below -->
|
||||
<div class="relative mt-1.5 h-3">
|
||||
{gaugeTicks.map((t) => (
|
||||
<div class="absolute" style={`left: ${t.pct}%; transform: translateX(-50%);`}>
|
||||
<div class="w-px h-2 bg-[color:var(--border)] mx-auto"></div>
|
||||
<div class="text-[10px] font-mono text-muted-foreground mt-1 text-center">{t.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Band detail rows -->
|
||||
<div class="mt-10 grid md:grid-cols-2 lg:grid-cols-5 gap-px bg-[color:var(--border)] rounded-[10px] overflow-hidden ring-1 ring-[color:var(--border)]">
|
||||
{bands.map((b) => {
|
||||
const t = toneClass(b.tone);
|
||||
return (
|
||||
<div class="bg-white p-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class={`w-2 h-2 rounded-full ${t.dot}`}></span>
|
||||
<span class="text-[13px] font-semibold text-heading">{b.name}</span>
|
||||
</div>
|
||||
<div class="mt-4 space-y-1.5 text-[11.5px] font-mono">
|
||||
<div class="flex justify-between gap-2"><span class="text-muted-foreground">Spam</span><span class="text-heading font-semibold text-right">{b.spam}</span></div>
|
||||
<div class="flex justify-between gap-2"><span class="text-muted-foreground">Complaint</span><span class="text-heading font-semibold text-right">{b.complaint}</span></div>
|
||||
<div class="flex justify-between gap-2"><span class="text-muted-foreground">Bounce</span><span class="text-heading font-semibold text-right">{b.bounce}</span></div>
|
||||
</div>
|
||||
<p class="mt-4 pt-4 border-t border-[color:var(--border)] text-[12px] text-foreground/70 leading-snug">{b.action}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<p class="mt-5 text-[11.5px] text-muted-foreground italic max-w-3xl">
|
||||
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%.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
AUTHENTICATION COVERAGE
|
||||
============================================================ -->
|
||||
<section class="border-y border-[color:var(--border)] bg-[color:var(--surface-1)]/40 py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="max-w-3xl mb-12">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Authentication coverage</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
SPF, DKIM, DMARC, PTR. Checked, not assumed.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
{auth.map((a) => (
|
||||
<div class="bg-white p-6">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-[color:var(--sky-6)] mb-3">DNS check</div>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">{a.tag}</div>
|
||||
<div class="text-[18px] font-semibold text-heading">{a.name}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/70 leading-relaxed">{a.body}</p>
|
||||
<p class="mt-2 text-[13px] text-foreground/70 leading-relaxed">{a.body}</p>
|
||||
<p class="mt-3 pt-3 border-t border-[color:var(--border)] text-[12px] text-foreground/60 leading-snug">
|
||||
<span class="font-mono uppercase tracking-[0.14em] text-[10px] text-muted-foreground block mb-1">on failure</span>
|
||||
{a.fail}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- THRESHOLDS COMPARISON -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-20">
|
||||
<!-- ============================================================
|
||||
SUPPRESSION ENGINE
|
||||
============================================================ -->
|
||||
<section class="bg-white py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Thresholds</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">We act weeks before providers do.</h2>
|
||||
<p class="mt-4 text-[15.5px] text-foreground/75 max-w-xl">
|
||||
Public thresholds vs. the bands we trigger on internally.
|
||||
</p>
|
||||
|
||||
<div class="mt-10 rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="grid grid-cols-[1.5fr_1fr_1fr_1fr] px-5 py-3 bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">
|
||||
<div>Provider</div>
|
||||
<div class="text-[color:var(--sky-7)]">We act at</div>
|
||||
<div>Warning</div>
|
||||
<div>Provider acts</div>
|
||||
<div class="grid lg:grid-cols-[1fr_1.8fr] gap-12 lg:gap-20 items-start">
|
||||
<div class="lg:sticky lg:top-24" style="align-self: start;">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Suppression engine</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Suppression is containment, not paperwork.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/70 leading-relaxed max-w-md">
|
||||
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.
|
||||
</p>
|
||||
<ul class="mt-6 space-y-2 text-[13.5px] text-foreground/80">
|
||||
<li class="flex items-start gap-2"><Icon name="check" size={13} class="text-[#0284c7] mt-1 shrink-0" /><span>Workspace-scoped suppression. Every campaign skips suppressed recipients on next tick.</span></li>
|
||||
<li class="flex items-start gap-2"><Icon name="check" size={13} class="text-[#0284c7] mt-1 shrink-0" /><span>RFC 8058 one-click unsubscribe. List-Unsubscribe-Post handled before the receipt page renders.</span></li>
|
||||
<li class="flex items-start gap-2"><Icon name="check" size={13} class="text-[#0284c7] mt-1 shrink-0" /><span>Inbound reply classifier auto-suppresses common opt-out phrasing.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
{bands.map((b) => (
|
||||
<div class="grid grid-cols-[1.5fr_1fr_1fr_1fr] px-5 py-3.5 border-b border-[color:var(--border)] last:border-b-0 text-[13.5px] items-baseline">
|
||||
<div class="font-medium text-heading">{b.provider}</div>
|
||||
<div><span class="inline-flex items-center px-1.5 py-0.5 rounded font-mono text-[12px] bg-emerald-50 text-emerald-700">{b.us}</span></div>
|
||||
<div><span class="inline-flex items-center px-1.5 py-0.5 rounded font-mono text-[12px] bg-amber-50 text-amber-700">{b.warn}</span></div>
|
||||
<div><span class="inline-flex items-center px-1.5 py-0.5 rounded font-mono text-[12px] bg-rose-50 text-rose-700">{b.kill}</span></div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden">
|
||||
<div class="grid grid-cols-[minmax(0,1.4fr)_90px_minmax(0,1.8fr)_minmax(0,2.5fr)] px-5 py-3 bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground">
|
||||
<div>Trigger</div>
|
||||
<div>Scope</div>
|
||||
<div>Source</div>
|
||||
<div>Behaviour</div>
|
||||
</div>
|
||||
))}
|
||||
{suppression.map((s) => (
|
||||
<div class="grid grid-cols-[minmax(0,1.4fr)_90px_minmax(0,1.8fr)_minmax(0,2.5fr)] px-5 py-3.5 border-b border-[color:var(--border)] last:border-b-0 items-baseline gap-3">
|
||||
<div class="text-[13px] font-medium text-heading">{s.trigger}</div>
|
||||
<div>
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded text-[10px] font-mono uppercase tracking-[0.08em] bg-[color:var(--sky-1)] text-[#0369a1]">{s.scope}</span>
|
||||
</div>
|
||||
<div class="font-mono text-[11.5px] text-foreground/65 truncate">{s.source}</div>
|
||||
<div class="text-[12.5px] text-foreground/70 leading-snug">{s.note}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SIGNALS GRID -->
|
||||
<section class="py-16 md:py-20">
|
||||
<!-- ============================================================
|
||||
RECOMMENDED POSTURE FOR COLD SENDING
|
||||
============================================================ -->
|
||||
<section class="border-y border-[color:var(--border)] bg-[color:var(--surface-1)]/40 py-20 md:py-28">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Signals</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">What we watch, in real time.</h2>
|
||||
<div class="grid lg:grid-cols-[1fr_1.4fr] gap-12 lg:gap-20 items-start">
|
||||
<div class="lg:sticky lg:top-24" style="align-self: start;">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Cold sending posture</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Start small. Ramp boring.<br/>Stay under the cap.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/70 leading-relaxed max-w-md">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid sm:grid-cols-2 lg:grid-cols-4 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
{signals.map(([t, b]) => (
|
||||
<div class="bg-white p-5">
|
||||
<div class="text-[14.5px] font-semibold text-heading">{t}</div>
|
||||
<p class="mt-1.5 text-[12.5px] text-foreground/70 leading-relaxed">{b}</p>
|
||||
<div class="space-y-px">
|
||||
{posture.map((r, i) => (
|
||||
<div class="grid grid-cols-[140px_auto_1fr] gap-6 items-baseline py-5 border-b border-[color:var(--border)] last:border-b-0">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-muted-foreground">{r.stage}</div>
|
||||
<div class="flex items-baseline gap-1.5">
|
||||
<span class={`text-[28px] md:text-[36px] font-semibold tracking-[-0.025em] leading-none font-mono ${i === 2 ? 'text-[#0369a1]' : 'text-heading'}`}>{r.range}</span>
|
||||
</div>
|
||||
<p class="text-[13.5px] text-foreground/65 leading-relaxed">{r.note}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div class="pt-6 flex flex-wrap items-center gap-x-6 gap-y-1 text-[11.5px] font-mono text-muted-foreground">
|
||||
<span>600s minimum gap, always</span>
|
||||
<span class="w-1 h-1 rounded-full bg-[color:var(--border)]"></span>
|
||||
<span>Business hours by recipient TZ</span>
|
||||
<span class="w-1 h-1 rounded-full bg-[color:var(--border)]"></span>
|
||||
<span>Warmup runs in parallel with cold</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
BY THE NUMBERS · real product defaults
|
||||
============================================================ -->
|
||||
<section class="bg-white py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-10">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">By the numbers</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
The shape of the safety policy, in one strip.
|
||||
</h2>
|
||||
<p class="mt-3 text-[14px] text-foreground/65 leading-relaxed">
|
||||
Every number here is a real product default in the codebase, not a marketing rounding.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]">
|
||||
{numbers.map((n) => (
|
||||
<div class="bg-white p-5 md:p-6">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-[28px] md:text-[34px] font-semibold tracking-[-0.025em] font-mono text-heading">{n.v}</span>
|
||||
<span class="text-[12px] text-muted-foreground">{n.u}</span>
|
||||
</div>
|
||||
<div class="mt-2 text-[11.5px] uppercase tracking-[0.14em] font-mono text-foreground/70">{n.l}</div>
|
||||
<div class="mt-3 pt-3 border-t border-[color:var(--border)] text-[10.5px] font-mono text-muted-foreground truncate">{n.src}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- LEARN POINTERS -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-14 md:py-16">
|
||||
<div class="container-page grid lg:grid-cols-[1fr_2fr] gap-10">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Go deeper</div>
|
||||
<h2 class="text-[24px] md:text-[30px] font-semibold tracking-[-0.025em] text-heading leading-[1.1]">Full deliverability handbook.</h2>
|
||||
<!-- ============================================================
|
||||
FAQ · sticky title + accordion
|
||||
============================================================ -->
|
||||
<section class="bg-[color:var(--surface-1)]/40 border-y border-[color:var(--border)] py-20 md:py-24">
|
||||
<div class="container-page grid lg:grid-cols-[1fr_1.8fr] gap-12 lg:gap-20 items-start">
|
||||
<div class="lg:sticky lg:top-24" style="align-self: start;">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Deliverability FAQ</div>
|
||||
<h2 class="text-[28px] md:text-[36px] font-semibold tracking-[-0.025em] leading-[1.08] text-heading">
|
||||
Five questions we get most.
|
||||
</h2>
|
||||
<p class="mt-4 text-[14.5px] text-foreground/70 leading-relaxed">
|
||||
More depth in the <a class="text-[#0369a1] hover:text-[#075985] font-medium" href="/learn/deliverability/">deliverability handbook</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-2 gap-3">
|
||||
{[
|
||||
{ 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) => (
|
||||
<a href={p.h} class="group flex items-center justify-between p-4 rounded-[10px] bg-white ring-1 ring-[color:var(--border)] hover:ring-[color:var(--sky-6)]/40 transition-colors">
|
||||
<span class="text-[14px] font-medium text-foreground">{p.l}</span>
|
||||
<Icon name="arrowRight" size={13} class="text-muted-foreground group-hover:text-[color:var(--brand)] group-hover:translate-x-0.5 transition-all" />
|
||||
</a>
|
||||
|
||||
<div class="divide-y divide-[color:var(--border)]" data-faq>
|
||||
{faq.map(([q, a]) => (
|
||||
<div class="faq-row py-2">
|
||||
<button type="button" class="faq-trigger group w-full flex items-start justify-between gap-4 py-3 text-left" aria-expanded="false">
|
||||
<span class="text-[15.5px] font-medium text-heading group-hover:text-[#0369a1] transition-colors">{q}</span>
|
||||
<span class="faq-icon shrink-0 inline-flex items-center justify-center w-7 h-7 rounded-full bg-white ring-1 ring-[color:var(--border)] text-foreground/60 transition-transform duration-300 ease-out">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
||||
</span>
|
||||
</button>
|
||||
<div class="faq-panel grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out">
|
||||
<div class="overflow-hidden">
|
||||
<p class="pb-4 pr-12 text-[14px] text-foreground/75 leading-relaxed max-w-2xl">{a}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
const faq = document.querySelector('[data-faq]');
|
||||
if (!faq) return;
|
||||
faq.querySelectorAll('.faq-trigger').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const row = btn.closest('.faq-row');
|
||||
const panel = row.querySelector('.faq-panel');
|
||||
const icon = btn.querySelector('.faq-icon');
|
||||
const open = btn.getAttribute('aria-expanded') === 'true';
|
||||
btn.setAttribute('aria-expanded', String(!open));
|
||||
panel.style.gridTemplateRows = open ? '0fr' : '1fr';
|
||||
if (icon) icon.style.transform = open ? 'rotate(0deg)' : 'rotate(45deg)';
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Audit your sending health today."
|
||||
description="Connect a mailbox and get a full SPF / DKIM / DMARC / placement report in under five minutes."
|
||||
title="Audit a mailbox. See where it really stands."
|
||||
description="Connect a mailbox and get a full SPF, DKIM, DMARC, PTR, and placement report in under five minutes."
|
||||
primaryLabel="Run a free audit"
|
||||
primaryHref="https://app.warmbly.com/register"
|
||||
secondaryLabel="Read the handbook"
|
||||
secondaryHref="/learn/deliverability/"
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
+572
-73
@@ -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.',
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
<Legal
|
||||
title="Data Processing Addendum"
|
||||
description="The terms under which Warmbly processes personal data on behalf of customers acting as data controllers."
|
||||
effective="2026-05-01"
|
||||
<Layout
|
||||
title="Data processing addendum · Warmbly"
|
||||
description="The DPA between Warmbly (Processor) and Customer (Controller), governing how Warmbly processes personal data on behalf of customers."
|
||||
>
|
||||
<p>
|
||||
This Data Processing Addendum ("DPA") supplements the <a href="/terms/">Warmbly Terms of Service</a> 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.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
HERO
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<h2>1. Definitions</h2>
|
||||
<p>
|
||||
"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").
|
||||
</p>
|
||||
<div class="container-page relative pt-14 md:pt-20 pb-16 md:pb-20">
|
||||
<div class="max-w-3xl">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">Legal</span>
|
||||
Data processing
|
||||
</div>
|
||||
|
||||
<h2>2. Scope and roles</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<h1 class="mt-8 text-[40px] sm:text-5xl md:text-[60px] lg:text-[68px] font-semibold tracking-[-0.035em] leading-[1.0] text-white">
|
||||
Data processing<br/>addendum.
|
||||
</h1>
|
||||
|
||||
<h2>3. Processing details</h2>
|
||||
<ul>
|
||||
<li><strong>Subject matter:</strong> performance of the Warmbly services described in the Terms.</li>
|
||||
<li><strong>Duration:</strong> the duration of the customer agreement plus any retention period set out in the Privacy Policy.</li>
|
||||
<li><strong>Nature and purpose:</strong> hosting, storing, transmitting and processing customer content (contacts, sequences, sent and received mail metadata, replies, suppressions) to deliver the service.</li>
|
||||
<li><strong>Types of Personal Data:</strong> account data (customer staff), recipient data (contacts), mailbox content metadata, IP addresses, behavioural events.</li>
|
||||
<li><strong>Categories of Data Subjects:</strong> Controller's team members; Controller's contacts and recipients.</li>
|
||||
</ul>
|
||||
<p class="mt-6 text-[16.5px] md:text-[18px] text-white/85 max-w-2xl leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h2>4. Controller instructions</h2>
|
||||
<p>
|
||||
Warmbly processes Personal Data only on documented Controller instructions, including those given through the platform itself, unless required to do otherwise by applicable law.
|
||||
</p>
|
||||
<div class="mt-8 flex flex-wrap items-center gap-3">
|
||||
<div class="inline-flex items-center gap-2 h-9 px-3 rounded-[10px] bg-white/10 backdrop-blur ring-1 ring-white/30 text-[13px] text-white/95">
|
||||
<span class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-white/65">Last updated</span>
|
||||
<span class="font-mono text-white">{LAST_UPDATED}</span>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="inline-flex h-9 items-center gap-2 px-4 rounded-[10px] text-[13px] font-medium bg-white text-[#075985] hover:-translate-y-0.5 transition-all duration-200 ease-out shadow-[0_4px_14px_-2px_rgba(0,0,0,0.18)]"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
||||
<polyline points="7 10 12 15 17 10"/>
|
||||
<line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</svg>
|
||||
Download as PDF
|
||||
</a>
|
||||
<a
|
||||
href="/terms/"
|
||||
class="inline-flex h-9 items-center px-4 rounded-[10px] text-[13px] font-medium bg-white/10 backdrop-blur ring-1 ring-white/30 text-white hover:bg-white/20 hover:-translate-y-0.5 transition-all duration-200 ease-out"
|
||||
>
|
||||
Read the Terms
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>5. Confidentiality</h2>
|
||||
<p>
|
||||
Warmbly ensures personnel authorised to process Personal Data are subject to confidentiality obligations.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
BODY
|
||||
============================================================ -->
|
||||
<section class="py-12 md:py-16">
|
||||
<div class="container-page grid grid-cols-1 lg:grid-cols-12 gap-10 lg:gap-12">
|
||||
|
||||
<h2>6. Security</h2>
|
||||
<p>
|
||||
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 <a href="/trust/">/security</a>.
|
||||
</p>
|
||||
<!-- Sidebar TOC ----------------------------------------- -->
|
||||
<aside class="lg:col-span-3">
|
||||
<div class="lg:sticky lg:top-24">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-4">
|
||||
On this page
|
||||
</div>
|
||||
<nav>
|
||||
<ol class="space-y-0.5">
|
||||
{toc.map((s) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${s.id}`}
|
||||
class="group flex items-baseline gap-2.5 -mx-2 px-2 py-1.5 rounded-md text-[13px] leading-snug text-muted-foreground hover:text-foreground hover:bg-[color:var(--surface-2)]/60 transition-colors"
|
||||
>
|
||||
<span class="font-mono text-[10.5px] text-muted-foreground/70 group-hover:text-[#0369a1] tabular-nums">{s.num}</span>
|
||||
<span class="flex-1">{s.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h2>7. Sub-processors</h2>
|
||||
<p>
|
||||
The Controller authorises Warmbly to engage the sub-processors listed at <a href="/subprocessors/">/subprocessors</a>. Warmbly will give 30 days' notice of any new sub-processor; the Controller may object on reasonable data-protection grounds.
|
||||
</p>
|
||||
<div class="mt-8 pt-6 border-t border-[color:var(--border)]">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">
|
||||
Related
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
<li><a href="/terms/" class="text-[13px] text-muted-foreground hover:text-[color:var(--brand)]">Terms of Service</a></li>
|
||||
<li><a href="/privacy/" class="text-[13px] text-muted-foreground hover:text-[color:var(--brand)]">Privacy Policy</a></li>
|
||||
<li><a href="/subprocessors/" class="text-[13px] text-muted-foreground hover:text-[color:var(--brand)]">Sub-processors</a></li>
|
||||
<li><a href="/trust/" class="text-[13px] text-muted-foreground hover:text-[color:var(--brand)]">Trust and security</a></li>
|
||||
<li><a href="/acceptable-use/" class="text-[13px] text-muted-foreground hover:text-[color:var(--brand)]">Acceptable Use Policy</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<h2>8. International transfers</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- Main content ---------------------------------------- -->
|
||||
<article class="lg:col-span-9 max-w-3xl">
|
||||
|
||||
<h2>9. Data subject requests</h2>
|
||||
<p>
|
||||
Taking into account the nature of the processing, Warmbly will assist the Controller in responding to data subject requests through technical and organisational measures.
|
||||
</p>
|
||||
<!-- Lead-in -->
|
||||
<div class="pb-8 mb-10 border-b border-[color:var(--border)]">
|
||||
<p class="text-[15.5px] leading-relaxed text-foreground/85">
|
||||
This Data Processing Addendum (the <strong>"DPA"</strong>) supplements the Warmbly <a href="/terms/" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Terms of Service</a> (the <strong>"Agreement"</strong>) 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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15.5px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>10. Personal Data breaches</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- 01 Definitions -->
|
||||
<section id="definitions" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">01</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Definitions</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85 mb-5">
|
||||
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.
|
||||
</p>
|
||||
<dl class="divide-y divide-[color:var(--border)] rounded-xl ring-1 ring-[color:var(--border)] bg-white overflow-hidden">
|
||||
{definitions.map(([term, body]) => (
|
||||
<div class="grid grid-cols-1 md:grid-cols-[200px_minmax(0,1fr)] gap-1 md:gap-6 px-5 py-4">
|
||||
<dt class="text-[13.5px] font-semibold text-heading">{term}</dt>
|
||||
<dd class="text-[14px] leading-relaxed text-foreground/80">{body}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2>11. Audits</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- 02 Scope and roles -->
|
||||
<section id="scope-roles" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">02</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Scope and roles</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="/privacy/" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Privacy Policy</a> applies.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<h2>12. Return and deletion</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- 03 Subject matter and duration -->
|
||||
<section id="processing" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">03</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Subject matter, nature, purpose, and duration</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="#annex-1" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Annex 1</a>.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<h2>13. Liability</h2>
|
||||
<p>
|
||||
Each party's liability under this DPA is subject to the limitations and exclusions of liability set out in the Terms.
|
||||
</p>
|
||||
<!-- 04 Categories of data subjects -->
|
||||
<section id="data-subjects" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">04</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Categories of data subjects</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85 mb-4">
|
||||
The Personal Data processed under this DPA concerns the following categories of Data Subjects:
|
||||
</p>
|
||||
<ul class="space-y-2.5 text-[14.5px] leading-relaxed text-foreground/85 pl-5 list-disc">
|
||||
<li>Customer’s authorised users and team members who access the platform.</li>
|
||||
<li>Customer’s contacts, leads, and recipients to whom Customer sends mail through the platform.</li>
|
||||
<li>Senders and recipients of replies received in Customer’s connected mailboxes.</li>
|
||||
<li>Other natural persons whose Personal Data Customer chooses to upload, import, or otherwise process through the platform.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2>14. Order of precedence</h2>
|
||||
<p>
|
||||
In the event of any conflict between this DPA and the Terms, the DPA controls with respect to data protection matters.
|
||||
</p>
|
||||
<!-- 05 Categories of personal data -->
|
||||
<section id="data-categories" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">05</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Categories of personal data</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85 mb-4">
|
||||
The categories of Personal Data processed include:
|
||||
</p>
|
||||
<ul class="space-y-2.5 text-[14.5px] leading-relaxed text-foreground/85 pl-5 list-disc">
|
||||
<li><strong>Identifiers.</strong> Names, email addresses, IP addresses, user-agent strings.</li>
|
||||
<li><strong>Professional contact information.</strong> Company name, job title, phone number, social profile links, and similar business contact attributes Customer chooses to import.</li>
|
||||
<li><strong>Message content.</strong> Subject lines, message bodies, attachments, signatures, and metadata of mail sent or received through Customer’s connected mailboxes.</li>
|
||||
<li><strong>Behavioural signals.</strong> Open events, click events, replies, bounces, unsubscribes, suppression status, and warmup activity.</li>
|
||||
<li><strong>Authentication and configuration data.</strong> OAuth tokens, SMTP credentials, send-rate settings, and mailbox health state, stored using envelope encryption.</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<h2>15. Signed copies</h2>
|
||||
<p>
|
||||
A signed counter-signed copy of this DPA is available on request at <a href="mailto:legal@warmbly.com">legal@warmbly.com</a>.
|
||||
</p>
|
||||
</Legal>
|
||||
<!-- 06 Customer instructions -->
|
||||
<section id="instructions" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">06</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Customer instructions</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 07 Confidentiality -->
|
||||
<section id="confidentiality" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">07</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Confidentiality and personnel</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 08 Security measures -->
|
||||
<section id="security" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">08</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Security measures</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="#annex-2" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Annex 2</a> and described in more detail at <a href="/trust/" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">trust and security</a>.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 09 Sub-processors -->
|
||||
<section id="subprocessors" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">09</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Sub-processors</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="/subprocessors/" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">/subprocessors</a>.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 10 International transfers -->
|
||||
<section id="transfers" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">10</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">International data transfers</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="#annex-1" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Annex 1</a>, <a href="#annex-2" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Annex 2</a>, and <a href="#annex-3" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Annex 3</a> of this DPA.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="mailto:legal@warmbly.com" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">legal@warmbly.com</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 11 Data subject rights -->
|
||||
<section id="rights" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">11</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Data subject rights assistance</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="mailto:legal@warmbly.com" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">legal@warmbly.com</a> 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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 12 Breach notification -->
|
||||
<section id="breach" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">12</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Personal data breach notification</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85 mt-4 mb-3">
|
||||
The notification will, to the extent then known, include:
|
||||
</p>
|
||||
<ul class="space-y-2.5 text-[14.5px] leading-relaxed text-foreground/85 pl-5 list-disc">
|
||||
<li>The nature of the breach, including the categories and approximate number of Data Subjects and records concerned.</li>
|
||||
<li>The likely consequences of the breach.</li>
|
||||
<li>The measures Warmbly has taken or proposes to take to address the breach, including measures to mitigate its possible adverse effects.</li>
|
||||
<li>The name and contact details of a point of contact for further information.</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 13 Audits -->
|
||||
<section id="audits" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">13</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Audits and inspections</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 14 Return or deletion -->
|
||||
<section id="return-deletion" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">14</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Return or deletion at end of services</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 15 Liability -->
|
||||
<section id="liability" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">15</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Liability and limitations</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 16 Order of precedence -->
|
||||
<section id="precedence" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">16</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Order of precedence and severability</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 17 Governing law -->
|
||||
<section id="governing-law" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">17</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Governing law</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 18 Annex 1 -->
|
||||
<section id="annex-1" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">18</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Annex 1 · Processing details</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85 mb-5">
|
||||
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.
|
||||
</p>
|
||||
<div class="overflow-hidden rounded-xl ring-1 ring-[color:var(--border)] bg-white">
|
||||
<table class="w-full text-[14px]">
|
||||
<thead class="bg-[color:var(--surface-2)]/70 text-[11.5px] uppercase tracking-[0.12em] font-mono text-muted-foreground">
|
||||
<tr>
|
||||
<th class="text-left font-semibold px-5 py-3 w-[200px]">Field</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[color:var(--border)]">
|
||||
{annex1.map(([field, body]) => (
|
||||
<tr>
|
||||
<td class="px-5 py-4 align-top text-[13.5px] font-semibold text-heading">{field}</td>
|
||||
<td class="px-5 py-4 align-top text-[14px] leading-relaxed text-foreground/80">{body}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 19 Annex 2 -->
|
||||
<section id="annex-2" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">19</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Annex 2 · Security measures</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85 mb-5">
|
||||
The technical and organisational measures Warmbly applies to the processing of Personal Data are summarised below. The current detailed control set is published at <a href="/trust/" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">trust and security</a>.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
{annex2.map((group) => (
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-white overflow-hidden">
|
||||
<div class="flex items-center gap-2.5 px-5 py-3 border-b border-[color:var(--border)] bg-[color:var(--surface-2)]/60">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-[color:var(--brand)]"></span>
|
||||
<div class="text-[13px] font-semibold text-heading tracking-[-0.005em]">{group.cat}</div>
|
||||
</div>
|
||||
<ul class="divide-y divide-[color:var(--border)]">
|
||||
{group.items.map((item) => (
|
||||
<li class="px-5 py-3 text-[14px] leading-relaxed text-foreground/80">{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 20 Annex 3 -->
|
||||
<section id="annex-3" class="scroll-mt-24 mb-14">
|
||||
<div class="flex items-baseline gap-3 mb-4">
|
||||
<span class="font-mono text-[12px] tracking-[0.18em] text-muted-foreground uppercase">20</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Annex 3 · Approved sub-processors</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a href="/subprocessors/" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">/subprocessors</a>. That page is incorporated into this DPA by reference and forms part of Annex 3.
|
||||
</p>
|
||||
<div class="mt-5 rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-2)]/60 p-5">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground mb-2">Subscribe to changes</div>
|
||||
<p class="text-[14px] leading-relaxed text-foreground/85">
|
||||
Email <a href="mailto:legal@warmbly.com?subject=Subprocessor%20updates" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">legal@warmbly.com</a> 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.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Closing block -->
|
||||
<div class="mt-16 pt-8 border-t border-[color:var(--border)]">
|
||||
<div class="rounded-2xl ring-1 ring-[color:var(--border)] bg-white p-6 md:p-7">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-2">Signatures</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">
|
||||
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 <a href="mailto:legal@warmbly.com" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">legal@warmbly.com</a>.
|
||||
</p>
|
||||
<div class="mt-5 flex flex-wrap items-center gap-x-6 gap-y-2 text-[12px] font-mono text-muted-foreground">
|
||||
<span><span class="text-foreground/55">Version</span> <span class="text-foreground">2026.05.27</span></span>
|
||||
<span><span class="text-foreground/55">Last updated</span> <span class="text-foreground">{LAST_UPDATED}</span></span>
|
||||
<span><span class="text-foreground/55">Contact</span> <a href="mailto:legal@warmbly.com" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">legal@warmbly.com</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
+456
-90
@@ -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;
|
||||
---
|
||||
<Legal
|
||||
title="Privacy Policy"
|
||||
description="What data Warmbly collects, why we collect it, and how we handle it."
|
||||
effective="2026-05-01"
|
||||
<Layout
|
||||
title="Privacy Policy · Warmbly"
|
||||
description="What data Warmbly collects, why we collect it, where we store it, and the controls you have over your information."
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
HERO · HeroAtmosphere (shared)
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden print:hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<h2>1. The data we collect</h2>
|
||||
<h3>Account data</h3>
|
||||
<ul>
|
||||
<li>Name, email address, password hash, profile photo (if uploaded).</li>
|
||||
<li>Workspace name, billing address, payment method (handled by Stripe).</li>
|
||||
<li>Team-member emails and roles you add to the workspace.</li>
|
||||
</ul>
|
||||
<div class="container-page relative pt-14 md:pt-20 pb-28 md:pb-36">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Legal
|
||||
</span>
|
||||
Privacy Policy
|
||||
</div>
|
||||
|
||||
<h3>Mailbox connection data</h3>
|
||||
<ul>
|
||||
<li>Mailbox provider, mailbox address, OAuth tokens or SMTP credentials.</li>
|
||||
<li>Display name, signature, send rate configuration.</li>
|
||||
<li>Encrypted credentials are stored using envelope encryption with AWS KMS.</li>
|
||||
</ul>
|
||||
<h1 class="mt-7 md:mt-9 text-[44px] sm:text-6xl md:text-[72px] font-semibold tracking-[-0.04em] leading-[0.98] text-white max-w-3xl">
|
||||
Privacy.
|
||||
</h1>
|
||||
|
||||
<h3>Customer content</h3>
|
||||
<ul>
|
||||
<li>Contacts, sequences, message templates, sent and received message metadata, replies, suppression entries.</li>
|
||||
<li>Files you upload (e.g. CSV imports).</li>
|
||||
</ul>
|
||||
<p class="mt-6 text-[17px] md:text-[19px] text-white/80 max-w-2xl leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h3>Telemetry</h3>
|
||||
<ul>
|
||||
<li>Server-side logs (IP, user agent, timestamps, request paths) used for security, billing, and abuse prevention.</li>
|
||||
<li>Product analytics for feature usage and stability (workspace owner can disable for the workspace).</li>
|
||||
<li>Error reports (Sentry) — scrubbed of customer content.</li>
|
||||
</ul>
|
||||
<div class="mt-8 flex flex-wrap items-center gap-3">
|
||||
<div class="inline-flex items-center gap-2 h-9 px-3 rounded-full bg-white/12 backdrop-blur ring-1 ring-white/20 text-[12.5px] font-mono text-white/85">
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full bg-emerald-300"></span>
|
||||
Last updated {lastUpdated}
|
||||
</div>
|
||||
<a href="#data-we-collect" class="inline-flex items-center gap-1.5 h-9 px-3.5 rounded-full bg-white/10 hover:bg-white/15 ring-1 ring-white/20 text-[12.5px] text-white/90 transition-colors">
|
||||
Jump to data we collect
|
||||
<span aria-hidden="true">↓</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>2. How we use it</h2>
|
||||
<ul>
|
||||
<li>To provide the service: connect mailboxes, send mail, surface replies, run analytics you can read.</li>
|
||||
<li>To bill you accurately.</li>
|
||||
<li>To detect and prevent abuse, fraud, and platform misuse.</li>
|
||||
<li>To send transactional emails (account activity, security alerts, billing receipts).</li>
|
||||
<li>To improve the product based on aggregated usage patterns.</li>
|
||||
<li>To respond to support requests.</li>
|
||||
</ul>
|
||||
<p>
|
||||
We do not sell personal information to advertisers.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
PRINT HEADER · only visible when printed
|
||||
============================================================ -->
|
||||
<header class="hidden print:block px-6 pt-8 pb-4 border-b border-[color:var(--border)]">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-2">Warmbly Legal</div>
|
||||
<h1 class="text-[28px] font-semibold tracking-[-0.02em] text-heading">Privacy Policy</h1>
|
||||
<div class="mt-1 text-[12px] font-mono text-muted-foreground">Last updated {lastUpdated}</div>
|
||||
</header>
|
||||
|
||||
<h2>3. Legal bases (GDPR)</h2>
|
||||
<ul>
|
||||
<li>Performance of a contract — almost all account and service use.</li>
|
||||
<li>Legitimate interest — security, fraud prevention, product improvement.</li>
|
||||
<li>Consent — optional product analytics, optional marketing email subscriptions.</li>
|
||||
<li>Compliance with legal obligations — invoicing, tax records.</li>
|
||||
</ul>
|
||||
<!-- ============================================================
|
||||
BODY · sticky TOC + numbered sections
|
||||
============================================================ -->
|
||||
<section class="container-page py-14 md:py-20">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-10 lg:gap-14">
|
||||
|
||||
<h2>4. Sharing</h2>
|
||||
<p>
|
||||
We share data with vetted subprocessors as needed to deliver the service. The complete list is published at <a href="/subprocessors/">/subprocessors</a>. We do not share customer content with third parties for their own marketing.
|
||||
</p>
|
||||
<p>
|
||||
We may disclose data if required by law, court order, or to protect our rights or the safety of users.
|
||||
</p>
|
||||
<!-- TOC sidebar -->
|
||||
<aside class="lg:col-span-3 print:hidden">
|
||||
<div class="lg:sticky lg:top-24">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">On this page</div>
|
||||
<nav aria-label="Privacy Policy sections">
|
||||
<ol class="space-y-0.5">
|
||||
{sections.map((s) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${s.id}`}
|
||||
class="group flex items-baseline gap-2.5 text-[13px] -mx-2 px-2 py-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-[color:var(--surface-2)]/60 transition-colors"
|
||||
>
|
||||
<span class="font-mono text-[10.5px] text-muted-foreground/70 group-hover:text-foreground/70 tabular-nums">{s.n}</span>
|
||||
<span>{s.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<h2>5. International transfers</h2>
|
||||
<p>
|
||||
Customer content can be stored in the United States or the European Union depending on the data-residency setting on your plan. Transfers from the EU are subject to Standard Contractual Clauses.
|
||||
</p>
|
||||
<div class="mt-8 pt-6 border-t border-[color:var(--border)]">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">Related</div>
|
||||
<ul class="space-y-0.5">
|
||||
{docs.map((d) => (
|
||||
<li>
|
||||
<a
|
||||
href={d.href}
|
||||
class={
|
||||
'block text-[13px] -mx-2 px-2 py-1.5 rounded-md transition-colors ' +
|
||||
(path === d.href
|
||||
? 'bg-[color:var(--surface-2)] text-foreground font-medium'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-[color:var(--surface-2)]/60')
|
||||
}
|
||||
>
|
||||
{d.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<h2>6. Retention</h2>
|
||||
<ul>
|
||||
<li>Account data: while your account is active, plus a wind-down period of up to 30 days after closure.</li>
|
||||
<li>Customer content: while your account is active. On closure, we delete or anonymise within 30 days unless retention is required by law.</li>
|
||||
<li>Backups: rolling encrypted backups retained for up to 35 days.</li>
|
||||
<li>Telemetry: aggregated indefinitely; raw logs retained 30 days unless flagged for incident investigation.</li>
|
||||
</ul>
|
||||
<!-- Main content -->
|
||||
<article class="lg:col-span-9 max-w-3xl">
|
||||
<!-- Lead -->
|
||||
<div class="pb-8 mb-10 border-b border-[color:var(--border)]">
|
||||
<p class="text-[16px] md:text-[17px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[14px] text-muted-foreground">
|
||||
We use simple language on purpose. If anything here is unclear, write to <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]" href="mailto:privacy@warmbly.com">privacy@warmbly.com</a> and we will rewrite it.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>7. Your rights</h2>
|
||||
<p>
|
||||
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 <a href="mailto:privacy@warmbly.com">privacy@warmbly.com</a> and we will respond within 30 days.
|
||||
</p>
|
||||
<!-- ============================================================ -->
|
||||
<section id="who-we-are" class="scroll-mt-24 mt-12 first:mt-0">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">01</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Who we are</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
Privacy questions, data subject requests, and security reports all go to <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] underline-offset-2 hover:underline" href="mailto:privacy@warmbly.com">privacy@warmbly.com</a>. We try to acknowledge within two business days.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<h2>8. Security</h2>
|
||||
<p>
|
||||
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 <a href="/trust/">/security</a>.
|
||||
</p>
|
||||
<!-- ============================================================ -->
|
||||
<section id="data-we-collect" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">02</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Data we collect</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
We collect the smallest set of information we need to run the product, bill it, and keep it safe to use. Roughly four buckets.
|
||||
</p>
|
||||
|
||||
<h2>9. Children</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<div class="mt-6 grid gap-4">
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<h3 class="text-[15.5px] font-semibold text-heading">Account data</h3>
|
||||
<span class="text-[10.5px] font-mono uppercase tracking-[0.12em] text-muted-foreground">Controller</span>
|
||||
</div>
|
||||
<p class="mt-2 text-[14.5px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>10. Changes</h2>
|
||||
<p>
|
||||
We may update this policy. Material changes will be announced via email or in-app notice at least 30 days in advance.
|
||||
</p>
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<h3 class="text-[15.5px] font-semibold text-heading">Mailbox data</h3>
|
||||
<span class="text-[10.5px] font-mono uppercase tracking-[0.12em] text-muted-foreground">Processor</span>
|
||||
</div>
|
||||
<p class="mt-2 text-[14.5px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>11. Contact</h2>
|
||||
<p>
|
||||
Warmbly Labs, Inc. — 1209 N. Orange Street, Wilmington, DE 19801, USA. Privacy contact: <a href="mailto:privacy@warmbly.com">privacy@warmbly.com</a>.
|
||||
</p>
|
||||
</Legal>
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<h3 class="text-[15.5px] font-semibold text-heading">Message data</h3>
|
||||
<span class="text-[10.5px] font-mono uppercase tracking-[0.12em] text-muted-foreground">Processor</span>
|
||||
</div>
|
||||
<p class="mt-2 text-[14.5px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<div class="flex items-baseline justify-between gap-3">
|
||||
<h3 class="text-[15.5px] font-semibold text-heading">Usage data</h3>
|
||||
<span class="text-[10.5px] font-mono uppercase tracking-[0.12em] text-muted-foreground">Controller</span>
|
||||
</div>
|
||||
<p class="mt-2 text-[14.5px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="how-we-use-data" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">03</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">How we use your data</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<ul class="mt-5 space-y-3">
|
||||
{[
|
||||
['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]) => (
|
||||
<li class="flex gap-3">
|
||||
<span class="mt-1.5 inline-block w-1.5 h-1.5 rounded-full bg-[color:var(--brand)] shrink-0" aria-hidden="true"></span>
|
||||
<div>
|
||||
<span class="text-[14.5px] font-semibold text-heading">{k}.</span>
|
||||
<span class="text-[14.5px] text-foreground/85 leading-relaxed"> {v}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p class="mt-5 text-[14.5px] text-foreground/85 leading-relaxed">
|
||||
We do not sell personal information. We do not rent contact lists. We do not share message content with advertisers.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="legal-bases" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">04</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Legal bases for processing</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
Where data protection law requires a legal basis, we rely on one of the four below. Most of what we do sits under contract or legitimate interest.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 divide-y divide-[color:var(--border)] rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] overflow-hidden">
|
||||
<div class="p-5 grid grid-cols-1 sm:grid-cols-[160px_1fr] gap-3 sm:gap-6">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">Contract</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">We process the data you give us in order to deliver the product you signed up for. Without it we cannot send the mail or show you the results.</p>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-1 sm:grid-cols-[160px_1fr] gap-3 sm:gap-6">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">Legitimate interest</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">We have a legitimate interest in keeping the platform safe and reliable. That covers abuse prevention, security monitoring, deliverability analytics, and core product improvement.</p>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-1 sm:grid-cols-[160px_1fr] gap-3 sm:gap-6">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">Consent</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">For optional features such as product analytics opt-in and non-essential marketing email, we rely on your consent. You can withdraw it at any time without losing access to the service.</p>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-1 sm:grid-cols-[160px_1fr] gap-3 sm:gap-6">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">Legal obligation</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">Some data, like billing records and tax invoices, we have to keep for a period set by law. We also retain the minimum needed to respond to lawful requests.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="encryption-storage" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">05</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Encryption and storage</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 grid gap-4">
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<h3 class="text-[14.5px] font-semibold text-heading">Root of trust</h3>
|
||||
<p class="mt-1.5 text-[14px] leading-relaxed text-foreground/85">AWS KMS holds the customer master key. Plaintext data keys are never written to disk and never leave the encryption boundary in clear form.</p>
|
||||
</div>
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<h3 class="text-[14.5px] font-semibold text-heading">Per-user data key</h3>
|
||||
<p class="mt-1.5 text-[14px] leading-relaxed text-foreground/85">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.</p>
|
||||
</div>
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<h3 class="text-[14.5px] font-semibold text-heading">Key ciphertext</h3>
|
||||
<p class="mt-1.5 text-[14px] leading-relaxed text-foreground/85">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.</p>
|
||||
</div>
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<h3 class="text-[14.5px] font-semibold text-heading">Hot key cache</h3>
|
||||
<p class="mt-1.5 text-[14px] leading-relaxed text-foreground/85">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.</p>
|
||||
</div>
|
||||
<div class="rounded-xl ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)] p-5">
|
||||
<h3 class="text-[14.5px] font-semibold text-heading">Control plane</h3>
|
||||
<p class="mt-1.5 text-[14px] leading-relaxed text-foreground/85">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-[14px] text-muted-foreground leading-relaxed">
|
||||
Encryption boundaries are documented in the codebase under <code class="font-mono text-[0.92em] px-1.5 py-0.5 rounded bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)]">internal/app/cipher</code> and <code class="font-mono text-[0.92em] px-1.5 py-0.5 rounded bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)]">internal/infrastructure/kms</code>. Warmbly is open source, so the implementation can be inspected.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="subprocessors" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">06</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Subprocessors</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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 <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] underline-offset-2 hover:underline" href="/subprocessors/">/subprocessors</a>.
|
||||
</p>
|
||||
<p class="mt-4 text-[14.5px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="retention" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">07</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Data retention</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 grid gap-3">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[200px_1fr] gap-3 sm:gap-6 p-4 rounded-lg ring-1 ring-[color:var(--border)]">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">While the account is active</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">Account data, mailbox credentials, message data, and analytics live in the platform so the product can do its job.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[200px_1fr] gap-3 sm:gap-6 p-4 rounded-lg ring-1 ring-[color:var(--border)]">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">After account deletion</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">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.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[200px_1fr] gap-3 sm:gap-6 p-4 rounded-lg ring-1 ring-[color:var(--border)]">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">Billing records</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">Invoices and tax records are kept for the period required by applicable tax and accounting law, typically seven years.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[200px_1fr] gap-3 sm:gap-6 p-4 rounded-lg ring-1 ring-[color:var(--border)]">
|
||||
<div class="text-[12.5px] font-mono uppercase tracking-[0.1em] text-muted-foreground">Suppression lists</div>
|
||||
<p class="text-[14.5px] leading-relaxed text-foreground/85">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.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="your-rights" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">08</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Your rights</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<ul class="mt-5 grid sm:grid-cols-2 gap-3">
|
||||
{[
|
||||
['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]) => (
|
||||
<li class="p-4 rounded-lg ring-1 ring-[color:var(--border)] bg-[color:var(--surface-1)]">
|
||||
<div class="text-[13.5px] font-semibold text-heading">{k}</div>
|
||||
<p class="mt-1 text-[13.5px] leading-relaxed text-foreground/85">{v}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p class="mt-6 text-[14.5px] leading-relaxed text-foreground/85">
|
||||
To exercise any of these, email <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] underline-offset-2 hover:underline" href="mailto:privacy@warmbly.com">privacy@warmbly.com</a>. We will verify the request comes from you and respond within 30 days. If we cannot complete the request we will tell you why.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="international" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">09</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">International transfers</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="cookies" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">10</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Cookies</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
<p class="mt-4 text-[15px] leading-relaxed text-foreground/85">
|
||||
The full list, including third-party cookies set by Stripe and the CAPTCHA provider on relevant pages, is at <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] underline-offset-2 hover:underline" href="/cookies/">/cookies</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="children" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">11</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Children's data</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="changes" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">12</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Changes to this policy</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<section id="contact" class="scroll-mt-24 mt-14">
|
||||
<div class="flex items-baseline gap-3 mb-3">
|
||||
<span class="font-mono text-[11px] tracking-[0.12em] text-muted-foreground tabular-nums">13</span>
|
||||
<h2 class="text-[22px] md:text-[26px] font-semibold tracking-[-0.02em] text-heading">Contact</h2>
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
Privacy questions, data subject requests, breach reports, and supervisory authority correspondence all go to <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] underline-offset-2 hover:underline" href="mailto:privacy@warmbly.com">privacy@warmbly.com</a>. For general legal matters, write to <a class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] underline-offset-2 hover:underline" href="mailto:legal@warmbly.com">legal@warmbly.com</a>.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Footer note -->
|
||||
<div class="mt-16 pt-6 border-t border-[color:var(--border)] flex flex-wrap items-center justify-between gap-3 text-[12.5px] text-muted-foreground">
|
||||
<div>Last updated {lastUpdated}</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a class="hover:text-foreground transition-colors" href="/terms/">Terms</a>
|
||||
<a class="hover:text-foreground transition-colors" href="/dpa/">DPA</a>
|
||||
<a class="hover:text-foreground transition-colors" href="/subprocessors/">Subprocessors</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
:global(header), :global(footer), :global(nav) { display: none !important; }
|
||||
:global(body) { background: white !important; color: #000 !important; }
|
||||
article { max-width: none !important; }
|
||||
a { color: inherit !important; text-decoration: underline; }
|
||||
section[id] { break-inside: avoid; }
|
||||
h2 { break-after: avoid; }
|
||||
}
|
||||
</style>
|
||||
</Layout>
|
||||
|
||||
@@ -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;
|
||||
---
|
||||
<Legal
|
||||
title="Subprocessors"
|
||||
description="The third parties Warmbly engages to deliver the service. Updated whenever a subprocessor is added or changed."
|
||||
effective="2026-05-01"
|
||||
<Layout
|
||||
title="Subprocessors · Warmbly"
|
||||
description="The third-party services Warmbly uses to operate the platform. Vendors, purposes, data categories, regions, and links to each vendor's security and DPA pages."
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- =====================================================
|
||||
HERO · HeroAtmosphere, shorter than product pages.
|
||||
Eyebrow, title, last-updated badge, short copy.
|
||||
===================================================== -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<h2>Active subprocessors</h2>
|
||||
<div class="overflow-x-auto rounded-xl border border-border bg-card -mx-1">
|
||||
<table class="w-full text-[14px] min-w-[600px]">
|
||||
<thead class="bg-surface-2/70 text-[12px] uppercase tracking-wider text-muted-foreground">
|
||||
<tr>
|
||||
<th class="text-left font-semibold px-5 py-3">Subprocessor</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Purpose</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Region</th>
|
||||
<th class="text-left font-semibold px-5 py-3">Country</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-border">
|
||||
{rows.map((r) => (
|
||||
<tr>
|
||||
{r.map((c, i) => <td class={`px-5 py-3 ${i === 0 ? 'font-medium' : 'text-muted-foreground'}`}>{c}</td>)}
|
||||
</tr>
|
||||
<div class="container-page relative pt-14 md:pt-20 pb-16 md:pb-20">
|
||||
<div class="max-w-3xl">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">Legal</span>
|
||||
Subprocessors
|
||||
</div>
|
||||
|
||||
<h1 class="mt-6 md:mt-8 text-[40px] sm:text-[52px] md:text-[64px] font-semibold tracking-[-0.035em] leading-[1.02] text-white">
|
||||
The vendors behind<br/>the platform.
|
||||
</h1>
|
||||
|
||||
<p class="mt-5 text-[16px] md:text-[17.5px] text-white/80 max-w-2xl leading-relaxed">
|
||||
These are the third-party services Warmbly uses to operate the platform. We notify customers of material changes.
|
||||
</p>
|
||||
|
||||
<div class="mt-7 flex flex-wrap items-center gap-3 text-[12.5px]">
|
||||
<span class="inline-flex items-center gap-2 h-8 px-3 rounded-[8px] bg-white/12 backdrop-blur ring-1 ring-white/25 text-white font-mono">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
||||
Last updated {lastUpdated}
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-2 h-8 px-3 rounded-[8px] bg-white/10 backdrop-blur ring-1 ring-white/20 text-white/85 font-mono">
|
||||
{activeCount} active subprocessors
|
||||
</span>
|
||||
<a
|
||||
href="/dpa/"
|
||||
class="inline-flex items-center gap-1.5 h-8 px-3 rounded-[8px] bg-white/10 backdrop-blur ring-1 ring-white/20 text-white/90 hover:bg-white/20 hover:text-white transition-colors"
|
||||
>
|
||||
See the DPA
|
||||
<Icon name="arrowUpRight" size={12} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
SUBSCRIBE TO CHANGES · narrow strip above the table.
|
||||
Short copy + a mailto. We keep this above the table so
|
||||
the reader sees how to follow updates before scanning.
|
||||
===================================================== -->
|
||||
<section class="relative -mt-8 md:-mt-12 z-10">
|
||||
<div class="container-page">
|
||||
<div
|
||||
class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-5 md:p-6 flex flex-col md:flex-row md:items-center md:justify-between gap-4"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-[10px] bg-[color:var(--brand-soft)] text-[color:var(--brand)] ring-1 ring-[color:var(--brand)]/15 inline-flex items-center justify-center shrink-0">
|
||||
<Icon name="bell" size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-[14.5px] font-semibold text-heading">Subscribe to changes</div>
|
||||
<p class="mt-1 text-[13px] text-foreground/70 leading-relaxed max-w-xl">
|
||||
Get a {noticeWindowDays}-day advance notice whenever a new subprocessor is added or an existing one changes scope. One email per change, never marketing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={`mailto:${subscriberEmail}?subject=Subscribe%20to%20subprocessor%20updates`}
|
||||
class="inline-flex h-10 px-4 items-center gap-2 rounded-[10px] text-[13.5px] font-semibold bg-[color:var(--brand)] hover:bg-[color:var(--brand-strong)] text-white transition-colors shrink-0"
|
||||
>
|
||||
<Icon name="mail" size={14} />
|
||||
{subscriberEmail}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
SUBPROCESSORS TABLE · full-width, designer-grade.
|
||||
Desktop: real <table> with hover rows and a sticky-feeling
|
||||
header. Mobile: same data restated as stacked cards.
|
||||
===================================================== -->
|
||||
<section class="pt-14 md:pt-20 pb-16 md:pb-20">
|
||||
<div class="container-page">
|
||||
<div class="flex flex-col md:flex-row md:items-end md:justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Active subprocessors</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading leading-[1.1] max-w-2xl">
|
||||
Who touches your data, and why.
|
||||
</h2>
|
||||
</div>
|
||||
<p class="text-[13.5px] text-muted-foreground max-w-sm md:text-right">
|
||||
Region listed is where the vendor processes Warmbly customer data. Link points to the vendor's own security or DPA page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- DESKTOP TABLE -->
|
||||
<div
|
||||
class="hidden md:block rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-[13.5px]">
|
||||
<thead>
|
||||
<tr class="bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">
|
||||
<th scope="col" class="font-semibold px-5 py-3.5 w-[18%]">Vendor</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5 w-[30%]">Purpose</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5 w-[24%]">Data categories</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5 w-[14%]">Region</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5 w-[14%]">Reference</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[color:var(--border)]">
|
||||
{subprocessors.map((s) => (
|
||||
<tr class="hover:bg-[color:var(--surface-1)]/60 transition-colors align-top">
|
||||
<td class="px-5 py-4">
|
||||
<div class="font-semibold text-heading tracking-[-0.005em]">{s.vendor}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-foreground/80 leading-relaxed">{s.purpose}</td>
|
||||
<td class="px-5 py-4 text-foreground/75 leading-relaxed">{s.data}</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="inline-flex items-center font-mono text-[12px] text-foreground/75">
|
||||
{s.region}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<a
|
||||
href={s.link.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
class="inline-flex items-center gap-1.5 text-[12.5px] font-mono text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] break-all"
|
||||
>
|
||||
{s.link.label}
|
||||
<Icon name="arrowUpRight" size={11} />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE STACKED CARDS -->
|
||||
<div class="md:hidden space-y-3">
|
||||
{subprocessors.map((s) => (
|
||||
<div
|
||||
class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-5"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="text-[15px] font-semibold text-heading tracking-[-0.005em]">{s.vendor}</div>
|
||||
<span class="font-mono text-[11px] text-foreground/65 shrink-0">{s.region}</span>
|
||||
</div>
|
||||
|
||||
<dl class="mt-4 space-y-3">
|
||||
<div>
|
||||
<dt class="text-[10.5px] uppercase tracking-[0.14em] font-mono text-muted-foreground mb-1">Purpose</dt>
|
||||
<dd class="text-[13px] text-foreground/80 leading-relaxed">{s.purpose}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-[10.5px] uppercase tracking-[0.14em] font-mono text-muted-foreground mb-1">Data categories</dt>
|
||||
<dd class="text-[13px] text-foreground/75 leading-relaxed">{s.data}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<a
|
||||
href={s.link.href}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
class="mt-4 inline-flex items-center gap-1.5 text-[12.5px] font-mono text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] break-all"
|
||||
>
|
||||
{s.link.label}
|
||||
<Icon name="arrowUpRight" size={11} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Subscribe to changes</h2>
|
||||
<p>
|
||||
Email <a href="mailto:legal@warmbly.com?subject=Subprocessor%20updates">legal@warmbly.com</a> to be notified of subprocessor changes by email. We send a notice at least 30 days before any change that affects customer data processing.
|
||||
</p>
|
||||
</Legal>
|
||||
<p class="mt-5 text-[12px] text-muted-foreground font-mono">
|
||||
Links open the vendor's own security or privacy page. Warmbly does not republish or endorse those documents.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
HOW WE EVALUATE · short list, four cards.
|
||||
===================================================== -->
|
||||
<section class="bg-[color:var(--surface-2)] border-y border-[color:var(--border)] py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="grid lg:grid-cols-[1fr_2fr] gap-12 lg:gap-16">
|
||||
<div class="lg:sticky lg:top-24 self-start">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">How we evaluate</div>
|
||||
<h2 class="text-[24px] md:text-[32px] font-semibold tracking-[-0.025em] text-heading leading-[1.1]">
|
||||
Boring, well-operated, scoped tight.
|
||||
</h2>
|
||||
<p class="mt-4 text-[14.5px] text-foreground/75 leading-relaxed max-w-sm">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="grid sm:grid-cols-2 gap-4">
|
||||
{criteria.map((c) => (
|
||||
<li
|
||||
class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-6"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-[10px] bg-[color:var(--brand-soft)] text-[color:var(--brand)] ring-1 ring-[color:var(--brand)]/15 inline-flex items-center justify-center mb-4">
|
||||
<Icon name={c.icon} size={18} />
|
||||
</div>
|
||||
<div class="text-[14.5px] font-semibold text-heading">{c.title}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/75 leading-relaxed">{c.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
WHAT CHANGES WHEN WE ADD A SUBPROCESSOR · notice + window
|
||||
===================================================== -->
|
||||
<section class="py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">When this list changes</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading leading-[1.1] max-w-2xl">
|
||||
{noticeWindowDays} days notice. Real objection window.
|
||||
</h2>
|
||||
<p class="mt-4 text-[14.5px] text-foreground/75 leading-relaxed max-w-2xl">
|
||||
Adding a subprocessor is a contract change, not a backend tweak. We treat it like one.
|
||||
</p>
|
||||
|
||||
<ol class="mt-10 grid md:grid-cols-3 gap-5">
|
||||
{noticeSteps.map((s) => (
|
||||
<li
|
||||
class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-6 relative"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="text-[11px] font-mono text-[color:var(--brand)] tracking-[0.16em]">{s.n}</div>
|
||||
<div class="mt-3 text-[15px] font-semibold text-heading">{s.title}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/75 leading-relaxed">{s.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
|
||||
<div
|
||||
class="mt-8 rounded-[12px] bg-[color:var(--brand-soft)]/40 ring-1 ring-[color:var(--brand)]/20 p-5 flex items-start gap-4"
|
||||
>
|
||||
<div class="w-8 h-8 rounded-[8px] bg-[color:var(--brand)] text-white inline-flex items-center justify-center shrink-0">
|
||||
<Icon name="shield" size={15} />
|
||||
</div>
|
||||
<div class="text-[13.5px] text-foreground/85 leading-relaxed">
|
||||
The {noticeWindowDays}-day notice is contractual. It is restated in the <a href="/dpa/" class="font-semibold text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">Data Processing Addendum</a> section 7. The objection process there controls if there is any conflict with this page.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
PREVIOUS SUBPROCESSORS · empty-state aware.
|
||||
===================================================== -->
|
||||
<section class="bg-[color:var(--surface-2)] border-y border-[color:var(--border)] py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="flex flex-col md:flex-row md:items-end md:justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Previous subprocessors</div>
|
||||
<h2 class="text-[24px] md:text-[30px] font-semibold tracking-[-0.025em] text-heading leading-[1.1] max-w-2xl">
|
||||
Vendors we have removed or replaced.
|
||||
</h2>
|
||||
</div>
|
||||
<p class="text-[13.5px] text-muted-foreground max-w-sm md:text-right">
|
||||
When a vendor is retired, the row moves here with the date it was removed and the reason.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{hasPrevious ? (
|
||||
<div
|
||||
class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-[13.5px]">
|
||||
<thead>
|
||||
<tr class="bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">
|
||||
<th scope="col" class="font-semibold px-5 py-3.5">Vendor</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5">Purpose</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5">Region</th>
|
||||
<th scope="col" class="font-semibold px-5 py-3.5">Removed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[color:var(--border)]">
|
||||
{previous.map((p) => (
|
||||
<tr class="align-top">
|
||||
<td class="px-5 py-4 font-semibold text-heading">{p.vendor}</td>
|
||||
<td class="px-5 py-4 text-foreground/80">{p.purpose}</td>
|
||||
<td class="px-5 py-4 font-mono text-[12px] text-foreground/70">{p.region}</td>
|
||||
<td class="px-5 py-4 font-mono text-[12px] text-foreground/70">{p.data}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-8 md:p-10 text-center"
|
||||
style="box-shadow: var(--shadow-sm);"
|
||||
>
|
||||
<div class="w-12 h-12 mx-auto rounded-[12px] bg-[color:var(--surface-1)] text-muted-foreground ring-1 ring-[color:var(--border)] inline-flex items-center justify-center">
|
||||
<Icon name="layers" size={20} />
|
||||
</div>
|
||||
<div class="mt-4 text-[15px] font-semibold text-heading">No previous subprocessors yet.</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/70 max-w-md mx-auto leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Want the contract version of this list?"
|
||||
description="The Data Processing Addendum restates the subprocessor commitments above and the 30-day notice in contract form. We sign customer paper or our own."
|
||||
primaryLabel="Read the DPA"
|
||||
primaryHref="/dpa/"
|
||||
secondaryLabel="Email subprocessors team"
|
||||
secondaryHref={`mailto:${subscriberEmail}`}
|
||||
/>
|
||||
</Layout>
|
||||
|
||||
+404
-86
@@ -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/' },
|
||||
];
|
||||
---
|
||||
<Legal
|
||||
title="Terms of Service"
|
||||
description="The agreement between you and Warmbly Labs, Inc. when you use the Warmbly platform."
|
||||
effective="2026-05-01"
|
||||
<Layout
|
||||
title="Terms of service · Warmbly"
|
||||
description="The terms that govern your use of Warmbly. Plain English, scannable, with clearly numbered sections."
|
||||
>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
HERO · HeroAtmosphere (shorter than product heroes)
|
||||
============================================================ -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<h2>1. The service</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<div class="container-page relative pt-14 md:pt-20 pb-16 md:pb-20">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Legal
|
||||
</span>
|
||||
The agreement between you and Warmbly
|
||||
</div>
|
||||
|
||||
<h2>2. Accounts</h2>
|
||||
<ul>
|
||||
<li>You must be at least 18 years old and able to enter binding contracts to use the service.</li>
|
||||
<li>You are responsible for safeguarding your account credentials.</li>
|
||||
<li>You are responsible for the activity that occurs under your account, including activity by your team members and integrations you authorise.</li>
|
||||
</ul>
|
||||
<h1 class="mt-6 md:mt-8 text-[40px] sm:text-5xl md:text-[60px] font-semibold tracking-[-0.035em] leading-[1.02] text-white max-w-3xl">
|
||||
Terms of service.
|
||||
</h1>
|
||||
|
||||
<h2>3. Subscriptions and billing</h2>
|
||||
<ul>
|
||||
<li>Paid plans are billed monthly or annually in advance.</li>
|
||||
<li>Charges are mailbox-based — the connected mailbox count on your billing date drives the invoice.</li>
|
||||
<li>You may cancel at any time; the cancellation takes effect at the end of the current billing period.</li>
|
||||
<li>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.</li>
|
||||
</ul>
|
||||
<p class="mt-5 text-[16px] md:text-[17px] text-white/80 max-w-2xl leading-relaxed">
|
||||
These terms govern how you use Warmbly. We have written them in plain English so you can read them in one sitting. Read them.
|
||||
</p>
|
||||
|
||||
<h2>4. Acceptable use</h2>
|
||||
<p>
|
||||
Your use of Warmbly must comply with our <a href="/acceptable-use/">Acceptable Use Policy</a>. In particular, you may not use the service to:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Send unsolicited bulk email outside the scope of cold outreach permitted in the relevant jurisdiction.</li>
|
||||
<li>Send mail that violates CAN-SPAM, GDPR, CASL, PECR or any other applicable law.</li>
|
||||
<li>Forge sender identity or attempt to bypass authentication.</li>
|
||||
<li>Run mail that promotes deceptive, fraudulent, illegal or harmful content.</li>
|
||||
<li>Probe, scan or stress-test the platform without our written consent.</li>
|
||||
</ul>
|
||||
<div class="mt-8 flex flex-wrap items-center gap-x-6 gap-y-3 text-[12.5px] font-mono text-white/85">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex h-1.5 w-1.5 rounded-full bg-white/80"></span>
|
||||
<span class="uppercase tracking-[0.14em] text-white/55">Last updated</span>
|
||||
<span class="text-white">{lastUpdated}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex h-1.5 w-1.5 rounded-full bg-white/80"></span>
|
||||
<span class="uppercase tracking-[0.14em] text-white/55">Effective from</span>
|
||||
<span class="text-white">{effectiveFrom}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2>5. Suspension and termination</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- ============================================================
|
||||
BODY · Sticky sidebar TOC + numbered sections
|
||||
============================================================ -->
|
||||
<section class="relative bg-white">
|
||||
<div class="container-page py-14 md:py-20">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-10 lg:gap-14">
|
||||
|
||||
<h2>6. Intellectual property</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
By using the platform, you grant Warmbly the limited rights necessary to deliver the service (storing, processing, transmitting your content and credentials as required).
|
||||
</p>
|
||||
<!-- Sticky sidebar TOC -->
|
||||
<aside class="lg:col-span-3 print:hidden">
|
||||
<div class="lg:sticky lg:top-24">
|
||||
|
||||
<h2>7. Confidentiality</h2>
|
||||
<p>
|
||||
Each party will protect the other's confidential information using 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.
|
||||
</p>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">
|
||||
On this page
|
||||
</div>
|
||||
|
||||
<h2>8. Privacy</h2>
|
||||
<p>
|
||||
Our <a href="/privacy/">Privacy Policy</a> explains what data we collect and how we handle it. If you are a controller of personal data processed through the platform, our <a href="/dpa/">Data Processing Addendum</a> applies.
|
||||
</p>
|
||||
<nav aria-label="Table of contents" class="border-l border-[color:var(--border)]">
|
||||
<ul class="space-y-0.5">
|
||||
{sections.map((s) => (
|
||||
<li>
|
||||
<a
|
||||
href={`#${s.id}`}
|
||||
class="group flex items-baseline gap-3 -ml-px pl-4 pr-2 py-1.5 border-l border-transparent hover:border-[color:var(--brand)] hover:bg-[color:var(--surface-2)]/60 transition-colors rounded-r-md"
|
||||
>
|
||||
<span class="text-[10.5px] font-mono text-muted-foreground/70 group-hover:text-[color:var(--brand)] tabular-nums">{s.num}</span>
|
||||
<span class="text-[13px] text-foreground/80 group-hover:text-[color:var(--brand-strong)] leading-snug">{s.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<h2>9. Warranties and disclaimer</h2>
|
||||
<p>
|
||||
We provide the service on an "as is" and "as available" basis. To the maximum extent permitted by law, Warmbly disclaims all warranties, express or implied, including merchantability and fitness for a particular purpose.
|
||||
</p>
|
||||
<p>
|
||||
We do not guarantee any specific deliverability outcome, inbox placement rate, reply rate or revenue result. Email deliverability is the cumulative result of authentication, content, recipient behaviour and provider-side reputation, much of which is outside our direct control.
|
||||
</p>
|
||||
<div class="mt-8 pt-6 border-t border-[color:var(--border)]">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">
|
||||
Related
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
{legalDocs.map((d) => (
|
||||
<li>
|
||||
<a
|
||||
href={d.href}
|
||||
class={
|
||||
'block text-[13px] py-1 rounded-md transition-colors ' +
|
||||
(d.href === '/terms/'
|
||||
? 'text-foreground font-medium'
|
||||
: 'text-muted-foreground hover:text-[color:var(--brand-strong)]')
|
||||
}
|
||||
>
|
||||
{d.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>10. Limitation of liability</h2>
|
||||
<p>
|
||||
To the maximum extent permitted by law, Warmbly's aggregate liability for any claim arising under these Terms will not exceed the fees you paid to Warmbly in the 12 months preceding the event giving rise to the claim.
|
||||
</p>
|
||||
<p>
|
||||
Neither party will be liable for indirect, consequential, incidental, special or punitive damages, or for lost profits, lost revenue or lost data.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<h2>11. Indemnity</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- Main content column -->
|
||||
<article class="lg:col-span-9 max-w-3xl">
|
||||
|
||||
<h2>12. Changes to these terms</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- Intro card -->
|
||||
<div class="rounded-[14px] bg-[color:var(--surface-2)] ring-1 ring-[color:var(--border)] p-6 md:p-7 mb-12 print:bg-white print:ring-0 print:p-0 print:mb-8">
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-[color:var(--brand-strong)] mb-2">
|
||||
Summary
|
||||
</div>
|
||||
<p class="text-[15px] leading-relaxed text-foreground/85">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>13. Governing law</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<!-- Reusable type rules for all sections -->
|
||||
<div class="
|
||||
[&_h2]:scroll-mt-28 [&_h2]:text-[24px] [&_h2]:md:text-[28px] [&_h2]:font-semibold [&_h2]:tracking-[-0.022em] [&_h2]:text-heading [&_h2]:mt-14 [&_h2]:mb-4 [&_h2]:first:mt-0 [&_h2]:flex [&_h2]:items-baseline [&_h2]:gap-3
|
||||
[&_h3]:text-[15.5px] [&_h3]:font-semibold [&_h3]:tracking-[-0.01em] [&_h3]:text-heading [&_h3]:mt-7 [&_h3]:mb-2
|
||||
[&_p]:text-[15px] [&_p]:leading-[1.7] [&_p]:text-foreground/85 [&_p]:mt-3
|
||||
[&_ul]:list-disc [&_ul]:pl-5 [&_ul]:mt-3 [&_ul]:space-y-1.5 [&_ul]:text-[14.5px] [&_ul]:text-foreground/85
|
||||
[&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:mt-3 [&_ol]:space-y-1.5 [&_ol]:text-[14.5px] [&_ol]:text-foreground/85
|
||||
[&_a]:text-[color:var(--brand)] [&_a]:underline [&_a]:underline-offset-[3px] [&_a]:decoration-[color:var(--brand)]/30 hover:[&_a]:decoration-[color:var(--brand)] hover:[&_a]:text-[color:var(--brand-strong)]
|
||||
[&_code]:font-mono [&_code]:text-[0.92em] [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:bg-[color:var(--surface-2)] [&_code]:ring-1 [&_code]:ring-[color:var(--border)]
|
||||
">
|
||||
|
||||
<h2>14. Contact</h2>
|
||||
<p>
|
||||
Warmbly Labs, Inc. — 1209 N. Orange Street, Wilmington, DE 19801, USA. Email <a href="mailto:legal@warmbly.com">legal@warmbly.com</a>.
|
||||
</p>
|
||||
</Legal>
|
||||
<!-- 01 Acceptance -->
|
||||
<h2 id="acceptance">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">01</span>
|
||||
<span>Acceptance of terms</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
You must be at least 18 years old and legally able to enter into a contract.
|
||||
</p>
|
||||
|
||||
<!-- 02 Definitions -->
|
||||
<h2 id="definitions">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">02</span>
|
||||
<span>Definitions</span>
|
||||
</h2>
|
||||
<ul>
|
||||
<li><strong class="text-heading font-semibold">Account</strong>. The login and workspace you create on Warmbly.</li>
|
||||
<li><strong class="text-heading font-semibold">Service</strong>. The hosted Warmbly product, including the dashboard, API, worker software, and supporting infrastructure.</li>
|
||||
<li><strong class="text-heading font-semibold">Customer content</strong>. The data you upload or connect, such as contacts, mailbox credentials, campaigns, replies, and message bodies.</li>
|
||||
<li><strong class="text-heading font-semibold">Subscription</strong>. A paid plan billed on a recurring basis through our payment processor.</li>
|
||||
<li><strong class="text-heading font-semibold">Mailbox</strong>. An email account you connect to Warmbly for warmup or sending.</li>
|
||||
</ul>
|
||||
|
||||
<!-- 03 Account -->
|
||||
<h2 id="account">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">03</span>
|
||||
<span>Account registration and security</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
You agree to protect your credentials, use a strong password, and enable available account-security features. Notify us promptly at <a href="mailto:security@warmbly.com">security@warmbly.com</a> if you suspect unauthorized access.
|
||||
</p>
|
||||
<p>
|
||||
We may suspend access to your account if we reasonably believe it has been compromised, until you can confirm it is secure.
|
||||
</p>
|
||||
|
||||
<!-- 04 Acceptable use -->
|
||||
<h2 id="acceptable-use">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">04</span>
|
||||
<span>Acceptable use</span>
|
||||
</h2>
|
||||
<p>
|
||||
Your use of the Service must follow our <a href="/acceptable-use/">Acceptable Use Policy</a>. 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.
|
||||
</p>
|
||||
<p>
|
||||
You are also responsible for keeping your sender domain configured correctly, including SPF, DKIM, and DMARC alignment where relevant.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 05 Service availability -->
|
||||
<h2 id="availability">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">05</span>
|
||||
<span>Service availability and changes</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 06 Billing -->
|
||||
<h2 id="billing">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">06</span>
|
||||
<span>Subscription, billing, refunds</span>
|
||||
</h2>
|
||||
<h3>Subscriptions</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<h3>Payment processor</h3>
|
||||
<p>
|
||||
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 <a href="/subprocessors/">/subprocessors</a>.
|
||||
</p>
|
||||
<h3>Cancellation</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<h3>Refunds</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<h3>Taxes</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 07 IP -->
|
||||
<h2 id="ip">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">07</span>
|
||||
<span>Intellectual property</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 08 User content -->
|
||||
<h2 id="user-content">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">08</span>
|
||||
<span>User content and your data</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
How we handle personal data is explained in our <a href="/privacy/">Privacy Policy</a>. If you are a controller of personal data processed through the Service, our <a href="/dpa/">Data Processing Addendum</a> applies.
|
||||
</p>
|
||||
|
||||
<!-- 09 Confidentiality -->
|
||||
<h2 id="confidentiality">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">09</span>
|
||||
<span>Confidentiality</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 10 Third party -->
|
||||
<h2 id="third-party">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">10</span>
|
||||
<span>Third-party services</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 11 Disclaimers -->
|
||||
<h2 id="disclaimers">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">11</span>
|
||||
<span>Disclaimers</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Nothing in these Terms limits any warranty or right that cannot be limited under applicable law.
|
||||
</p>
|
||||
|
||||
<!-- 12 Liability -->
|
||||
<h2 id="liability">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">12</span>
|
||||
<span>Limitation of liability</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 13 Indemnification -->
|
||||
<h2 id="indemnification">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">13</span>
|
||||
<span>Indemnification</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 14 Termination -->
|
||||
<h2 id="termination">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">14</span>
|
||||
<span>Termination</span>
|
||||
</h2>
|
||||
<p>
|
||||
You may stop using the Service at any time and cancel your subscription from the dashboard.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 15 Governing law -->
|
||||
<h2 id="governing-law">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">15</span>
|
||||
<span>Governing law and jurisdiction</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 16 Changes -->
|
||||
<h2 id="changes">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">16</span>
|
||||
<span>Changes to these terms</span>
|
||||
</h2>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- 17 Contact -->
|
||||
<h2 id="contact">
|
||||
<span class="font-mono text-[14px] text-muted-foreground tabular-nums pt-1">17</span>
|
||||
<span>Contact</span>
|
||||
</h2>
|
||||
<p>
|
||||
Questions about these Terms? Email <a href="mailto:legal@warmbly.com">legal@warmbly.com</a> and we will get back to you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer note -->
|
||||
<div class="mt-16 pt-6 border-t border-[color:var(--border)] flex flex-wrap items-baseline justify-between gap-3 text-[12.5px] text-muted-foreground">
|
||||
<div>
|
||||
Last updated <span class="font-mono text-foreground/80">{lastUpdated}</span>. Effective <span class="font-mono text-foreground/80">{effectiveFrom}</span>.
|
||||
</div>
|
||||
<a href="#" class="text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] print:hidden">Back to top</a>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
aside { display: none !important; }
|
||||
section { padding: 0 !important; }
|
||||
h2 { break-after: avoid; page-break-after: avoid; }
|
||||
p, li { orphans: 3; widows: 3; }
|
||||
}
|
||||
</style>
|
||||
</Layout>
|
||||
|
||||
+513
-110
@@ -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' },
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="Trust · Warmbly"
|
||||
description="Status, security posture, subprocessors, compliance progress and how to reach our security team."
|
||||
description="How Warmbly protects your mailbox, your data and your reputation. Envelope encryption with AWS KMS, worker isolation, abuse controls and vulnerability reporting."
|
||||
>
|
||||
<!-- HERO: status-board style, all-green live look -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[420px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
|
||||
<div class="container-page relative pt-14 pb-16">
|
||||
<div class="flex items-start justify-between flex-wrap gap-6">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-3">Trust</div>
|
||||
<h1 class="text-[32px] md:text-[48px] font-semibold tracking-[-0.025em] leading-[1.06] text-white max-w-3xl">
|
||||
Operational truth, in one place.
|
||||
</h1>
|
||||
<p class="mt-4 text-[15.5px] text-white/75 max-w-xl">
|
||||
Uptime, security posture, subprocessors and compliance. Published, not promised.
|
||||
</p>
|
||||
</div>
|
||||
<a href="https://status.warmbly.com" class="inline-flex h-10 px-4 items-center gap-2 rounded-[10px] text-[13px] font-medium bg-white/12 backdrop-blur ring-1 ring-white/25 text-white hover:bg-white/20">
|
||||
<!-- =====================================================
|
||||
HERO. HeroAtmosphere + floating card below.
|
||||
===================================================== -->
|
||||
<section class="relative isolate overflow-hidden">
|
||||
<HeroAtmosphere />
|
||||
|
||||
<div class="container-page relative pt-16 md:pt-24 pb-44 md:pb-56 text-center">
|
||||
<div class="inline-flex items-center gap-2 h-7 pl-1 pr-3 rounded-full bg-white/15 backdrop-blur ring-1 ring-white/25 text-[12px] text-white/95 shadow-[0_4px_14px_-4px_rgba(0,0,0,0.25)]">
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded-full text-[10.5px] font-semibold uppercase tracking-[0.06em] bg-white" style="color:#0369a1;">
|
||||
Trust
|
||||
</span>
|
||||
Operational truth, not marketing.
|
||||
</div>
|
||||
|
||||
<h1 class="mt-8 md:mt-10 text-[44px] sm:text-6xl md:text-[72px] lg:text-[80px] font-semibold tracking-[-0.04em] leading-[0.98] text-white max-w-4xl mx-auto">
|
||||
How Warmbly protects<br/>your mailbox.
|
||||
</h1>
|
||||
<p class="mt-6 text-[17px] md:text-[19px] text-white/80 max-w-2xl mx-auto leading-relaxed">
|
||||
We hold mailbox credentials, message bodies, and recipient lists. That is a serious responsibility. This page documents the encryption model, the worker boundary, the abuse controls, and how to reach our security team.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="mailto:security@warmbly.com" class="inline-flex h-11 px-5 items-center gap-2 rounded-[10px] text-[14.5px] font-semibold bg-white hover:-translate-y-0.5 hover:shadow-[0_12px_28px_-6px_rgba(0,0,0,0.3)] transition-all duration-200 ease-out shadow-[0_4px_14px_-2px_rgba(0,0,0,0.18)]">
|
||||
<span style="color:#075985;">Report a vulnerability</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#075985" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://status.warmbly.com" class="inline-flex h-11 px-5 items-center gap-2 rounded-[10px] text-[14.5px] font-medium bg-white/10 backdrop-blur ring-1 ring-white/40 text-white hover:bg-white/20 hover:-translate-y-0.5 transition-all duration-200 ease-out">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-400"></span>
|
||||
status.warmbly.com
|
||||
<Icon name="arrowUpRight" size={12} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Live status board -->
|
||||
<div class="mt-10 grid sm:grid-cols-2 lg:grid-cols-5 gap-3">
|
||||
{status.map((s) => (
|
||||
<div class="rounded-[12px] bg-white/[0.06] backdrop-blur ring-1 ring-white/15 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[12px] font-medium text-white">{s.label}</div>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
||||
</div>
|
||||
<div class="mt-3 text-[18px] font-semibold text-white tracking-[-0.01em]">100%</div>
|
||||
<div class="text-[10.5px] font-mono text-white/55">p95 · {s.ms}ms</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- COMPLIANCE STRIP -->
|
||||
<section class="py-16 md:py-20">
|
||||
<!-- =====================================================
|
||||
FLOATING POSTURE CARD under hero. -mt-36 / -mt-44 pattern.
|
||||
===================================================== -->
|
||||
<section class="relative -mt-36 md:-mt-44 pb-16 md:pb-24 z-10">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Compliance</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Where we are, honestly.</h2>
|
||||
<div class="rounded-[16px] bg-white ring-1 ring-slate-200 overflow-hidden shadow-[0_40px_90px_-30px_rgba(2,32,71,0.35),0_12px_30px_-12px_rgba(15,23,42,0.12)]">
|
||||
|
||||
<div class="mt-10 grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{compliance.map((c) => (
|
||||
<div class="rounded-[12px] bg-white ring-1 ring-[color:var(--border)] p-5" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[14.5px] font-semibold text-heading">{c.name}</div>
|
||||
<span class={`inline-flex items-center gap-1 h-5 px-1.5 rounded text-[10.5px] font-medium ${
|
||||
c.tone === 'emerald' ? 'bg-emerald-50 text-emerald-700' :
|
||||
c.tone === 'sky' ? 'bg-sky-50 text-sky-700' :
|
||||
'bg-slate-100 text-slate-600'
|
||||
}`}>
|
||||
<span class={`w-1 h-1 rounded-full ${
|
||||
c.tone === 'emerald' ? 'bg-emerald-500' :
|
||||
c.tone === 'sky' ? 'bg-sky-500' : 'bg-slate-400'
|
||||
}`}></span>
|
||||
{c.state}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-2 text-[11.5px] font-mono text-muted-foreground">{c.est}</div>
|
||||
<div class="px-5 md:px-7 py-4 border-b border-slate-200 flex items-center gap-3">
|
||||
<div>
|
||||
<div class="text-[10px] uppercase tracking-[0.14em] text-slate-400 font-medium">Posture</div>
|
||||
<div class="mt-0.5 text-[13px] text-slate-900 font-medium">What we have shipped, and what is on the roadmap.</div>
|
||||
</div>
|
||||
<a href="/subprocessors/" class="ml-auto text-[12px] font-medium text-[color:var(--brand)] hover:text-[color:var(--brand-strong)] inline-flex items-center gap-1">
|
||||
Subprocessors <Icon name="arrowUpRight" size={11} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3">
|
||||
{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 (
|
||||
<div class={`p-5 md:p-6 border-b border-slate-200 sm:[&:nth-child(odd)]:border-r sm:last:border-b-0 ${borderRight} ${borderBottomLg}`}>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="text-[14px] font-semibold text-slate-900 tracking-[-0.01em]">{p.label}</div>
|
||||
<span class={`inline-flex items-center gap-1 h-5 px-1.5 rounded text-[10.5px] font-medium ${pillBg}`}>
|
||||
<span class={`w-1 h-1 rounded-full ${dot}`}></span>
|
||||
{stateLabel}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-2 text-[12.5px] text-slate-500 leading-relaxed">{p.note}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-center text-[12px] text-muted-foreground max-w-2xl mx-auto">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
ENCRYPTION FLOW. The real envelope model.
|
||||
===================================================== -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Encryption model</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Envelope encryption, with AWS KMS at the root.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/75 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Flow card -->
|
||||
<div class="mt-10 rounded-[16px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="grid md:grid-cols-4">
|
||||
{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 (
|
||||
<div class={`p-5 md:p-6 ${borderRight} ${borderBottom} border-[color:var(--border)] relative`}>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="font-mono text-[11px] text-muted-foreground tracking-[0.14em]">{f.n}</div>
|
||||
<span class="inline-flex items-center h-5 px-1.5 rounded text-[10px] font-medium bg-sky-50 text-sky-700">{f.tag}</span>
|
||||
</div>
|
||||
<div class="mt-3 text-[15px] font-semibold text-heading tracking-[-0.01em]">{f.title}</div>
|
||||
<p class="mt-2 text-[13px] text-foreground/70 leading-relaxed">{f.body}</p>
|
||||
{showArrow && (
|
||||
<div class="hidden md:flex absolute right-[-10px] top-1/2 -translate-y-1/2 w-5 h-5 rounded-full bg-white ring-1 ring-[color:var(--border)] items-center justify-center z-10">
|
||||
<Icon name="arrowRight" size={10} class="text-[color:var(--sky-4)]" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<!-- Footer summary strip -->
|
||||
<div class="px-5 md:px-7 py-4 bg-[color:var(--surface-1)] border-t border-[color:var(--border)] flex flex-wrap items-center gap-x-6 gap-y-2 text-[12px] text-muted-foreground">
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span> Plaintext DEK cached in Redis with TTL</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span> Encrypted DEK persisted in DynamoDB</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span> AES-256-GCM authenticated encryption</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source anchors. Honest pointer to actual code. -->
|
||||
<div class="mt-6 grid sm:grid-cols-2 gap-2">
|
||||
{[
|
||||
'internal/app/cipher/cipher.go',
|
||||
'internal/app/cipher/encrypt.go',
|
||||
'internal/infrastructure/kms/encryption.go',
|
||||
'internal/repository/dynamo_user_encrypted_keys.go',
|
||||
].map((p) => (
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-[8px] bg-white ring-1 ring-[color:var(--border)] font-mono text-[12px] text-foreground/75">
|
||||
<Icon name="code" size={12} class="text-[color:var(--sky-4)]" />
|
||||
{p}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SECURITY HIGHLIGHTS -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-20">
|
||||
<div class="container-page grid lg:grid-cols-2 gap-10 items-start">
|
||||
<!-- =====================================================
|
||||
WORKER BOUNDARY.
|
||||
===================================================== -->
|
||||
<section class="py-16 md:py-24">
|
||||
<div class="container-page grid lg:grid-cols-[1fr_1.2fr] gap-10 lg:gap-16 items-start">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Security posture</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-md">Envelope encryption with AWS KMS.</h2>
|
||||
<p class="mt-4 text-[14.5px] text-foreground/75 leading-relaxed max-w-md">
|
||||
Per-user data encryption keys. Encrypted DEK in DynamoDB, plaintext briefly cached. Workers never touch Postgres.
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Worker boundary</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Workers do not touch your database.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/75 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
<a href="/contact/?topic=security" class="mt-5 inline-flex items-center gap-1.5 text-[13.5px] font-medium text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">
|
||||
Request the security pack <Icon name="arrowRight" size={12} />
|
||||
<p class="mt-3 text-[14.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
<a href="/learn/architecture/" class="mt-5 inline-flex items-center gap-1.5 text-[13.5px] font-medium text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">
|
||||
Read the architecture <Icon name="arrowRight" size={12} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
{[
|
||||
'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) => (
|
||||
<div class="flex items-start gap-3 p-3 rounded-[10px] bg-white ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
<Icon name="shield" size={14} class="text-[color:var(--sky-6)] mt-0.5 shrink-0" />
|
||||
<span class="text-[13.5px] text-foreground/85">{b}</span>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="px-5 py-3 border-b border-[color:var(--border)] bg-[color:var(--surface-1)] flex items-center gap-2">
|
||||
<Icon name="cpu" size={14} class="text-[color:var(--sky-5)]" />
|
||||
<div class="text-[12px] font-mono text-muted-foreground tracking-[0.06em]">cmd/worker · network surface</div>
|
||||
</div>
|
||||
<div class="divide-y divide-[color:var(--border)]">
|
||||
{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 (
|
||||
<div class="grid grid-cols-[1fr_auto_auto] items-center gap-3 px-5 py-3.5">
|
||||
<div class="text-[13.5px] font-semibold text-heading">{row.k}</div>
|
||||
<div class="text-[12.5px] text-foreground/70 font-mono">{row.v}</div>
|
||||
<span class={`inline-flex items-center gap-1 h-5 px-1.5 rounded text-[10.5px] font-medium ${pill}`}>
|
||||
<span class={`w-1 h-1 rounded-full ${dot}`}></span>
|
||||
{isNever ? 'never' : 'allowed'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
DATA WE COLLECT / WE NEVER COLLECT.
|
||||
===================================================== -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl mb-10">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Data handling</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
What we hold, and what we refuse to hold.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/75 leading-relaxed">
|
||||
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/.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid lg:grid-cols-2 gap-5">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="px-5 py-3 border-b border-[color:var(--border)] flex items-center gap-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-sky-500"></span>
|
||||
<div class="text-[12px] uppercase tracking-[0.14em] font-mono text-muted-foreground">What we collect</div>
|
||||
</div>
|
||||
<ul class="divide-y divide-[color:var(--border)]">
|
||||
{collect.map((c) => (
|
||||
<li class="px-5 py-3.5 flex items-start gap-3">
|
||||
<Icon name="check" size={14} class="text-sky-600 mt-0.5 shrink-0" />
|
||||
<span class="text-[13.5px] text-foreground/85 leading-relaxed">{c}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="px-5 py-3 border-b border-[color:var(--border)] flex items-center gap-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-rose-500"></span>
|
||||
<div class="text-[12px] uppercase tracking-[0.14em] font-mono text-muted-foreground">What we never collect</div>
|
||||
</div>
|
||||
<ul class="divide-y divide-[color:var(--border)]">
|
||||
{neverCollect.map((c) => (
|
||||
<li class="px-5 py-3.5 flex items-start gap-3">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-rose-500 mt-0.5 shrink-0" aria-hidden="true">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
<span class="text-[13.5px] text-foreground/85 leading-relaxed">{c}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
ABUSE AND SAFETY POSTURE.
|
||||
===================================================== -->
|
||||
<section class="py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="max-w-2xl">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Abuse and safety</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Layered controls, not one brittle gate.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/75 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
{safety.map((s) => (
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-5" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="inline-flex items-center justify-center w-9 h-9 rounded-[10px] bg-sky-50 text-[color:var(--sky-5)]">
|
||||
<Icon name={s.icon} size={16} />
|
||||
</div>
|
||||
<div class="mt-4 text-[14.5px] font-semibold text-heading tracking-[-0.01em]">{s.title}</div>
|
||||
<p class="mt-1.5 text-[13px] text-foreground/70 leading-relaxed">{s.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SUBPROCESSORS TABLE -->
|
||||
<section class="py-16 md:py-20">
|
||||
<!-- =====================================================
|
||||
SUBPROCESSORS SUMMARY + LINK.
|
||||
===================================================== -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Subprocessors</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Who touches your data.</h2>
|
||||
<div class="flex items-end justify-between flex-wrap gap-4 mb-8">
|
||||
<div class="max-w-xl">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Subprocessors</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">Who else touches your data.</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/75 leading-relaxed">
|
||||
A short summary of the third parties Warmbly engages. The full list, with regions and country of processing, lives at /subprocessors/.
|
||||
</p>
|
||||
</div>
|
||||
<a href="/subprocessors/" class="inline-flex h-10 px-4 items-center gap-2 rounded-[10px] text-[13px] font-medium bg-white ring-1 ring-[color:var(--border)] text-heading hover:bg-[color:var(--surface-2)]">
|
||||
Full subprocessor list <Icon name="arrowUpRight" size={12} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="grid grid-cols-[1.2fr_2fr_1.2fr] px-5 py-3 bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="grid grid-cols-[1.2fr_2fr_1.2fr] px-5 py-3 bg-[color:var(--surface-1)] border-b border-[color:var(--border)] text-[10.5px] uppercase tracking-[0.14em] font-mono text-muted-foreground">
|
||||
<div>Subprocessor</div>
|
||||
<div>Purpose</div>
|
||||
<div>Location</div>
|
||||
<div>Region</div>
|
||||
</div>
|
||||
{subs.map(([s, p, l]) => (
|
||||
{subs.map((s) => (
|
||||
<div class="grid grid-cols-[1.2fr_2fr_1.2fr] px-5 py-3.5 border-b border-[color:var(--border)] last:border-b-0 text-[13.5px] items-baseline">
|
||||
<div class="font-semibold text-heading">{s}</div>
|
||||
<div class="text-foreground/75">{p}</div>
|
||||
<div class="font-mono text-[12.5px] text-foreground/70">{l}</div>
|
||||
<div class="font-semibold text-heading">{s.name}</div>
|
||||
<div class="text-foreground/75">{s.use}</div>
|
||||
<div class="font-mono text-[12.5px] text-foreground/70">{s.region}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p class="mt-4 text-[12px] text-muted-foreground">
|
||||
We give 30 days notice before adding any subprocessor that processes customer Personal Data.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
RESPONSIBLE DISCLOSURE.
|
||||
===================================================== -->
|
||||
<section class="py-16 md:py-24">
|
||||
<div class="container-page grid lg:grid-cols-[1.1fr_1fr] gap-10 lg:gap-16 items-start">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Responsible disclosure</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
|
||||
Found something. Tell us first.
|
||||
</h2>
|
||||
<p class="mt-4 text-[15px] text-foreground/75 leading-relaxed">
|
||||
We welcome reports from security researchers and operators. Send a description, a proof of concept, and any logs to <span class="font-mono text-heading">security@warmbly.com</span>. We will acknowledge quickly, triage in the open, and keep you posted until the fix ships.
|
||||
</p>
|
||||
<p class="mt-3 text-[14.5px] text-foreground/70 leading-relaxed">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 inline-flex items-center gap-2 px-3 py-2 rounded-[10px] bg-white ring-1 ring-[color:var(--border)]">
|
||||
<Icon name="mail" size={14} class="text-[color:var(--sky-5)]" />
|
||||
<a href="mailto:security@warmbly.com" class="font-mono text-[13px] text-heading hover:text-[color:var(--brand-strong)]">security@warmbly.com</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="px-5 py-3 border-b border-[color:var(--border)] bg-[color:var(--surface-1)] text-[12px] uppercase tracking-[0.14em] font-mono text-muted-foreground">
|
||||
Response SLA
|
||||
</div>
|
||||
<div class="divide-y divide-[color:var(--border)]">
|
||||
{disclosure.map((d) => (
|
||||
<div class="grid grid-cols-[140px_1fr] gap-3 px-5 py-3.5 items-baseline">
|
||||
<div class="text-[12.5px] uppercase tracking-[0.12em] font-mono text-muted-foreground">{d.k}</div>
|
||||
<div class="text-[13.5px] text-foreground/85">{d.v}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- =====================================================
|
||||
STATUS + CHANGELOG QUICK LINKS.
|
||||
===================================================== -->
|
||||
<section class="bg-[color:var(--surface-1)] border-t border-[color:var(--border)] py-12 md:py-16">
|
||||
<div class="container-page grid sm:grid-cols-2 gap-4">
|
||||
<a href="https://status.warmbly.com" class="group rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-5 flex items-start gap-4 hover:-translate-y-0.5 transition-transform" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="inline-flex items-center justify-center w-10 h-10 rounded-[10px] bg-emerald-50">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-[14.5px] font-semibold text-heading">Live status</div>
|
||||
<div class="mt-1 text-[12.5px] text-foreground/70 leading-relaxed">API, worker fleet, webhooks, realtime and inbox sync. Component-level history at <span class="font-mono">status.warmbly.com</span>.</div>
|
||||
</div>
|
||||
<Icon name="arrowUpRight" size={14} class="text-muted-foreground group-hover:text-[color:var(--brand)] mt-1" />
|
||||
</a>
|
||||
|
||||
<a href="/changelog/" class="group rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-5 flex items-start gap-4 hover:-translate-y-0.5 transition-transform" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="inline-flex items-center justify-center w-10 h-10 rounded-[10px] bg-sky-50 text-[color:var(--sky-5)]">
|
||||
<Icon name="list" size={16} />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-[14.5px] font-semibold text-heading">Changelog</div>
|
||||
<div class="mt-1 text-[12.5px] text-foreground/70 leading-relaxed">Security-relevant changes are flagged. Subprocessor additions are announced 30 days ahead.</div>
|
||||
</div>
|
||||
<Icon name="arrowUpRight" size={14} class="text-muted-foreground group-hover:text-[color:var(--brand)] mt-1" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Need our security package or DPA signed?"
|
||||
description="48-hour DPA turnaround. Full security pack on request."
|
||||
primaryLabel="Request security pack"
|
||||
primaryHref="/contact/?topic=security"
|
||||
title="Want the full security package?"
|
||||
description="DPA, security questionnaire, and architecture deep dive on request. Replies from a human within one business day."
|
||||
primaryLabel="Email security@warmbly.com"
|
||||
primaryHref="mailto:security@warmbly.com"
|
||||
secondaryLabel="See subprocessors"
|
||||
secondaryHref="/subprocessors/"
|
||||
/>
|
||||
</Layout>
|
||||
</content>
|
||||
</invoke>
|
||||
Reference in New Issue
Block a user