Email for software companies: one place for marketing campaigns, product loops and transactional mail, with events sent from your app driving who receives what.
Build me email for a software product, replacing Loops — and know what makes this ALMOST. The idea worth copying is **one contact record shared by campaigns, sequences and transactional mail**, so a person is never emailed twice by two systems that do not know about each other. That is a schema decision and it is free. What is not free is deliverability on a warmed sending domain, and the discipline that stops a product mailing somebody four times in an hour. STACK - Node 20+ with Fastify, server-rendered HTML - SQLite through better-sqlite3, WAL mode - A sending provider through its API - A worker for sending, separate from the web process - Caddy in front THE DATA MODEL, AND THE ONE THING THAT MATTERS - contacts: id, email, external_id, name, properties_json, subscribed_marketing, unsubscribed_at, bounced_at, complained_at, created_at, last_seen_at - **One contacts table.** The person who receives a campaign, a lifecycle sequence and a password reset is the same row. That single identity is why this product exists, and every other design here follows from it - events: id, contact_id, name, properties_json, at — sent from your application, and what drives everything - campaigns, sequences, transactional_templates — three kinds of message over one contact - sends: id, contact_id, kind, source_id, message_id, sent_at, opened_at, clicked_at, bounced_at, complained_at — one table for every message, whatever sent it. That is what makes the frequency rules possible - enrolments: id, sequence_id, contact_id, current_step, next_send_at, status, stop_reason - suppression: email_hash, reason, at — global and permanent FREQUENCY, WHICH IS THE FEATURE NOBODY BUILDS - Because every send is in one table, you can ask 'how many messages has this person had today' before sending. Do it - A cap per contact per day and per week for anything that is not transactional, configurable, enforced in the send path rather than in each campaign - Transactional mail always goes: a receipt or a password reset is never suppressed by a marketing cap. That distinction is a column, and it must be right - A quiet period after a sequence step, so a campaign does not land an hour later - This costs an afternoon and it is the difference between a product that communicates and one people mute EVENTS DRIVING MESSAGES - Your application sends named events with properties. A sequence is triggered by an event, filtered by properties, and stopped by another event - 'Signed up' starts onboarding; 'created first project' stops it. That stop condition is what makes lifecycle mail feel intelligent rather than mechanical - Idempotency by event id, so a retried call does not enrol twice - Every enrolment records why it started and why it stopped SENDING - One delivery row per contact per message, written before sending, which makes a resume exact and a duplicate impossible - The worker claims a batch with a lease and continues after a crash exactly where it stopped - Rate limited to what the provider accepts - A test send required before any campaign DELIVERABILITY - Separate subdomains for marketing and transactional, each with SPF, DKIM and DMARC. A password reset that fails because a campaign got complaints is a support disaster, and sharing a domain is how that happens - Warm slowly; watch the complaint rate; suppress hard bounces immediately and complaints permanently - One-click unsubscribe with no login, plus the List-Unsubscribe headers — and it must unsubscribe from marketing without breaking transactional THE MESSAGE - Markdown or a small block editor producing tables and inline styles, with the plain-text alternative generated from the same source - Personalisation from contact properties with a default for a missing value - Preview at phone and desktop width, and in the client that renders with the Word engine OPERATIONS - .env: DATABASE_PATH, BASE_URL, PROVIDER credentials, FROM_ADDRESS, SESSION_SECRET, HASH_SALT - Migrations on boot, each once; nightly backup off the machine - Health endpoint reporting bounce rate, complaint rate and any contact near the frequency cap WHAT MATTERS MOST One contacts table, one sends table, and the frequency cap enforced in the send path. That is the whole idea, and it is a schema rather than a feature.
What you lose
- One contact record shared by campaigns, sequences and transactional mail, so a person is never emailed twice by two systems
- Deliverability on a managed sending domain, warmed for you
- A visual sequence builder with branching on events your app sends
If you would rather not build
- Customer.io, for the same shape as a service
What it costs
as published on their pricing page
| Plan | Billed monthly | Billed yearly | Last read |
|---|---|---|---|
| — | $49/mo | — | — |
Their pricing page is where these came from. Seeing a different price? Tell us.
The escape hatch
open source · no votes, no paid placement
Listmonk
$0Self-hosted campaigns with a subscriber database and an API.
knadh/listmonkfree · open source
React Email
$0Build email templates as components; renders to Outlook-safe HTML.
resend/react-emailfree · open source
Why this verdict
our own opinion · changed only by a person
54/100
Verdict kinda at 54. The single contact record is the good idea here and is easy to copy; the branching builder and deliverability are the weekend.
History
tracked since 10 Aug 2026 · nothing is ever overwritten
Questions about Loops
answered from the record above
Is Loops free?
No — the plan we track is $49 a month. Pro from around $49/month billed monthly for 5,000 contacts, rising with the contact count.
Can you replace Loops by building your own?
ALMOST. A weekend of work, and real gaps remain. Replacement score 54 out of 100, build time a weekend. Read what you lose before you decide.
How much does Loops cost?
$49 a month on Pro — $588 a year. Recorded 9 Aug 2026.
What do you lose by replacing Loops?
One contact record shared by campaigns, sequences and transactional mail, so a person is never emailed twice by two systems; Deliverability on a managed sending domain, warmed for you; A visual sequence builder with branching on events your app sends. If any of those carry weight for you, keep paying.
Is there an open-source alternative to Loops?
Yes: Listmonk, React Email. The prompt on this page is for when you want it your way instead.
Related entries
same category first, most replaced first
Every week, something stops being worth paying for.
New verdicts, prices that moved, entries added. One email a week. Unsubscribe in one click. Nothing is being sent yet — your address is kept here, and the first issue is the first thing it is used for.
free forever · no tracking pixel · stored here, never passed to anyone

