lfr

module
v0.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT

README

lfr

CI codecov Release Go Reference

Your own mail server and newsletters in one Go binary on a $5 VPS, run by your coding agent from the terminal. No admin UI, no per-subscriber bill.

curl -fsSL https://lfr.email/install.sh | sudo sh

On the server it goes to /usr/local/bin. Without sudo it installs to ~/.local/bin, which is enough on a machine that only drives a server over ssh.

lfr runs mail for your domains from one binary on one small server: mailboxes, and newsletters with their websites. An agent operates it through a CLI, on the server or over ssh; there is no admin UI, no remote API and no monthly bill beyond the server (and a relay's cents per thousand mails where the provider blocks port 25).

  • A mail server: any name@domain in seconds, aliases, catch-alls, plus-addressing, forwarding, full-text search, replies, and a spam filter that learns from every message you move. DKIM (RSA and Ed25519), SPF, DMARC, MTA-STS and TLS reporting for every domain; lfr domain add prints exactly the DNS records to publish and lfr doctor checks them.
  • Newsletters: lists with double opt-in and stored consent proof, a welcome issue, issues written in Markdown with their images, checked before sending, rendered per reader with one-click unsubscribe (RFC 8058) and unsubscribe by reply, click and reply counts, bounce and complaint handling that pauses a list before it hurts your domains. Each list gets a fast website (issues, archive, feed, signup) on its own host with HTTPS.
  • A writing floor: every issue is held to its newsletter's brief and to rules against the habits of generated prose, with an offline estimate of whether AI-text detectors would flag it. The rules and the agent skills rest on built-in research (lfr evidence): 136 reader and writer voices, 20 newsletters and 144 standards, studies and benchmarks, quoted verbatim.

Pure Go, SQLite, one process. About 25 MB of memory idle; comfortable on a 512 MB droplet with dozens of domains.

Install

The script at the top installs the latest release for Linux and macOS, amd64 and arm64, after checking it against the release checksums. With Homebrew: brew install anonyfox/tap/lfr. With Go 1.26 or newer: go install github.com/Anonyfox/lfr/cmd/lfr@latest.

1. A mail server

On the server (a DigitalOcean droplet named mail.example.com, for example):

sudo lfr setup --hostname mail.example.com --acme-email you@example.com --systemd
lfr domain add example.com            # prints every DNS record to publish
lfr domain dns example.com --check    # every record ok?
lfr doctor                            # reverse DNS, blocklists, outbound, certificates, disk, queue

setup creates /var/lib/lfr, a system user and a hardened systemd service allowed to bind ports 25, 80 and 443. On Cloudflare, set the records to "DNS only"; lfr domain dns example.com --zone prints them as a zone file Cloudflare imports.

lfr delivers straight to recipients' mail servers when outbound port 25 is open. Many cloud providers block it; DigitalOcean blocks ports 25, 465 and 587 on every droplet. Then lfr hands finished, DKIM-signed messages to a relay on an alternate port for the last hop:

echo "$SES_SMTP_PASSWORD" | lfr relay smtps://AKIA...@email-smtp.eu-central-1.amazonaws.com:2465 --spf include:amazonses.com
lfr relay       # the route, a login test, and webhook URLs for the relay's bounce notifications

Amazon SES costs $0.10 to $0.16 per 1,000 messages; SMTP2GO has a free tier. See operations for both.

2. Mailboxes

lfr mailbox add hello@example.com --name "Example"
lfr mailbox alias hi@example.com hello@example.com
lfr mail ls --unread
lfr mail read 42
lfr mail reply 42 --md - <<< "Thanks, Ann. Feed it twice a day."
lfr mail mv 43 spam             # teaches the spam filter
lfr events --follow             # new mail, bounces, signups as they happen

3. Your agent

lfr skills install              # setup, mailbox, research, concept, launch, issue, learn
export LFR_REMOTE=you@mail.example.com   # every command, from your laptop, over ssh

Every command accepts --json; exit codes are 0 (ok), 1 (failed), 2 (wrong usage or invalid input) and 3 (server not running). Human output ends with Next: and the step that usually follows. SKILL.md is the entry point for agents.

4. A newsletter

The list lives on the server; the newsletter's editorial work lives in a project folder:

lfr list add bakers --name "Bakers Weekly" --from news@example.com --host news.example.com
lfr init bakers --list bakers --owner you@example.com
cd bakers && lfr check          # names the next step and its skill, every time

lfr check walks a newsletter through research (readers' verbatim voices, peer newsletters), the brief (concept.md: promise, cadence, format, length, subjects, voice, guardrails), the launch (lfr push copies the promise, cadence and welcome issue to the list), and then every issue: plan, draft, fix, test, approve, send, learn. Issues are Markdown files, with images next to them:

---
subject: Why your starter smells like nail polish
preheader: It is hungry, not dead, and two feeds fix it
idea: I001
---
# Your starter is hungry

It smells of acetone because it ran out of food. Feed it twice a day.

![The starter after two feeds, doubled in its jar](images/starter-day-2.jpg)
lfr issue put issues/acetone.md                 # create or update by slug, upload its images
lfr issue test acetone you@example.com
lfr issue send acetone --at 2026-10-23T07:00:00+02:00
lfr issue show acetone --learn                  # the learnings.md entry, two days later

Readers sign up at https://news.example.com/ (or any form that posts email to /subscribe), confirm by clicking a button, and leave with one click. Published issues live at /issues/<slug>. lfr site bakers --eject site copies the website templates into the project for editing and lfr push uploads them; lfr rules lists every check with the research behind it.

Documentation

  • Architecture: packages, data, the request paths.
  • AGENTS.md: how to change things: a command, a migration, a rule, a skill.
  • Operations: DNS, relays, deliverability, backups, upgrades, troubleshooting.
  • Contributing: building, testing, fuzzing, releasing.
  • The lfr.email Grok Bot: the handoff a Grok Bot follows to set up and share it.

MIT licensed.

Directories

Path Synopsis
cmd
lfr command
Command lfr runs and operates a newsletter, list-website and mailbox server for many small domains: lfr serve runs it, every other command manages it over a local socket.
Command lfr runs and operates a newsletter, list-website and mailbox server for many small domains: lfr serve runs it, every other command manages it over a local socket.
internal
addr
Package addr normalizes email addresses, domain names and host names, the one place deciding what lfr accepts as an address.
Package addr normalizes email addresses, domain names and host names, the one place deciding what lfr accepts as an address.
aitext
Package aitext estimates whether text-detection tools would flag prose as written by a language model, and says why.
Package aitext estimates whether text-detection tools would flag prose as written by a language model, and says why.
api
Package api defines the arguments and results of every control operation, shared by the CLI and the server so both always agree.
Package api defines the arguments and results of every control operation, shared by the CLI and the server so both always agree.
backup
Package backup verifies streamed backups before marking them complete.
Package backup verifies streamed backups before marking them complete.
blob
Package blob stores raw messages as immutable files named by their SHA-256, so a message delivered to several mailboxes is kept once and a crash never leaves a half-written file behind.
Package blob stores raw messages as immutable files named by their SHA-256, so a message delivered to several mailboxes is kept once and a crash never leaves a half-written file behind.
certs
Package certs provides TLS certificates from Let's Encrypt for every host lfr serves (mail host, list websites, MTA-STS hosts), renewing them automatically, with a self-signed fallback so STARTTLS keeps working before DNS is in place.
Package certs provides TLS certificates from Let's Encrypt for every host lfr serves (mail host, list websites, MTA-STS hosts), renewing them automatically, with a self-signed fallback so STARTTLS keeps working before DNS is in place.
cli
Package cli is the lfr command line: the only way to operate a server.
Package cli is the lfr command line: the only way to operate a server.
concept
Package concept reads concept.md, the editorial brief of a newsletter: who it is for, what it promises, how each issue is built, how often it comes, how long it is, how it sounds, and when to worry.
Package concept reads concept.md, the editorial brief of a newsletter: who it is for, what it promises, how each issue is built, how often it comes, how long it is, how it sounds, and when to worry.
config
Package config is the typed view of the instance settings kept in the database.
Package config is the typed view of the instance settings kept in the database.
craft
Package craft is lfr's own research on newsletter craft, embedded in the binary: verbatim voices of readers and writers, peer newsletters, and evidence from standards, studies, benchmarks and guides.
Package craft is lfr's own research on newsletter craft, embedded in the binary: verbatim voices of readers and writers, peer newsletters, and evidence from standards, studies, benchmarks and guides.
ctl
Package ctl is the local control protocol between the lfr CLI and the running server: one JSON request per connection over a unix socket that only the server's user (and group) can open.
Package ctl is the local control protocol between the lfr CLI and the running server: one JSON request per connection over a unix socket that only the server's user (and group) can open.
dnsbl
Package dnsbl looks up connecting IP addresses in DNS blocklists, telling a listing apart from the error codes lists return to resolvers they refuse.
Package dnsbl looks up connecting IP addresses in DNS blocklists, telling a listing apart from the error codes lists return to resolvers they refuse.
dnsx
Package dnsx is the DNS interface every lookup in lfr goes through, so tests run against a fake zone and production gets timeouts and an optional fixed resolver.
Package dnsx is the DNS interface every lookup in lfr goes through, so tests run against a fake zone and production gets timeouts and an optional fixed resolver.
dnsx/dnsxtest
Package dnsxtest is an in-memory DNS zone for tests.
Package dnsxtest is an in-memory DNS zone for tests.
domains
Package domains manages the mail domains lfr serves: their DKIM keys, the postmaster mailbox every domain must have, and the exact DNS records an operator has to publish for mail and websites to work.
Package domains manages the mail domains lfr serves: their DKIM keys, the postmaster mailbox every domain must have, and the exact DNS records an operator has to publish for mail and websites to work.
editorial
Package editorial checks the words of an issue: subject, preheader, length, links, images, tone and the brief's own phrases.
Package editorial checks the words of an issue: subject, preheader, length, links, images, tone and the brief's own phrases.
emailmd
Package emailmd renders Markdown into what a newsletter needs: email HTML with every style inlined (mail clients ignore most stylesheets), clean HTML for the web archive, and a readable plain-text part.
Package emailmd renders Markdown into what a newsletter needs: email HTML with every style inlined (mail clients ignore most stylesheets), clean HTML for the web archive, and a readable plain-text part.
engine
Package engine assembles lfr: it opens the data directory, wires the services together, runs the SMTP and HTTP servers, the delivery queue and the scheduler, and answers the CLI over the control socket.
Package engine assembles lfr: it opens the data directory, wires the services together, runs the SMTP and HTTP servers, the delivery queue and the scheduler, and answers the CLI over the control socket.
events
Package events records what happens (mail received, subscriptions, sends, bounces, opens, clicks) and streams it live to `lfr events --follow`.
Package events records what happens (mail received, subscriptions, sends, bounces, opens, clicks) and streams it live to `lfr events --follow`.
feedback
Package feedback reads what the mail world reports back: delivery status notifications (bounces), abuse feedback reports (spam complaints), DMARC aggregate and TLS reports, and relay webhooks.
Package feedback reads what the mail world reports back: delivery status notifications (bounces), abuse feedback reports (spam complaints), DMARC aggregate and TLS reports, and relay webhooks.
finding
Package finding is the one shape every check in lfr reports in: a rule ID, a severity, where, what is wrong and how to fix it.
Package finding is the one shape every check in lfr reports in: a rule ID, a severity, where, what is wrong and how to fix it.
i18n
Package i18n holds the few sentences lfr itself writes to readers, in English and German.
Package i18n holds the few sentences lfr itself writes to readers, in English and German.
inbound
Package inbound is the SMTP server on port 25: it receives mail for lfr's domains, refuses abuse as early and cheaply as possible, authenticates and scores what remains, and hands accepted messages to the engine.
Package inbound is the SMTP server on port 25: it receives mail for lfr's domains, refuses abuse as early and cheaply as possible, authenticates and scores what remains, and hands accepted messages to the engine.
issues
Package issues is the newsletter itself: issues written in Markdown, checked before sending, rendered per recipient with signed unsubscribe, click and open links, queued to every confirmed member, and counted afterwards.
Package issues is the newsletter itself: issues written in Markdown, checked before sending, rendered per recipient with signed unsubscribe, click and open links, queued to every confirmed member, and counted afterwards.
lists
Package lists manages newsletter lists and their audience: contacts, memberships with double opt-in and stored consent proof, unsubscribes, suppression after bounces and complaints, and import and export.
Package lists manages newsletter lists and their audience: contacts, memberships with double opt-in and stored consent proof, unsubscribes, suppression after bounces and complaints, and import and export.
mailauth
Package mailauth authenticates arriving mail (SPF, DKIM, DMARC with alignment) and DKIM-signs leaving mail.
Package mailauth authenticates arriving mail (SPF, DKIM, DMARC with alignment) and DKIM-signs leaving mail.
mailbox
Package mailbox is the plain mailbox side of lfr: addresses that receive and send like any mail account, with aliases, catch-alls, plus-addressing, forwarding, folders, flags and full-text search.
Package mailbox is the plain mailbox side of lfr: addresses that receive and send like any mail account, with aliases, catch-alls, plus-addressing, forwarding, folders, flags and full-text search.
msg
Package msg reads and writes RFC 5322 messages: parsing what arrives (tolerant of the malformed mail spam and old clients produce), composing what lfr sends, and turning HTML into readable text.
Package msg reads and writes RFC 5322 messages: parsing what arrives (tolerant of the malformed mail spam and old clients produce), composing what lfr sends, and turning HTML into readable text.
outbound
Package outbound is the delivery queue: every message lfr sends is a row that survives restarts, is built and DKIM-signed at send time, handed to a relay or straight to the recipient's MX, and retried with backoff until it is delivered, refused for good or five days old.
Package outbound is the delivery queue: every message lfr sends is a row that survives restarts, is built and DKIM-signed at send time, handed to a relay or straight to the recipient's MX, and retried with backoff until it is delivered, refused for good or five days old.
project
Package project is a newsletter's editorial workspace on disk: research, the concept, the backlog of ideas, issue drafts and what each sent issue taught.
Package project is a newsletter's editorial workspace on disk: research, the concept, the backlog of ideas, issue drafts and what each sent issue taught.
prose
Package prose holds writing to the floor readers expect from a person: no stock phrases of generated copy, none of the structural habits current models have (correcting claims nobody made, announcing instead of saying, recap endings, label-colon bullets, pet words), concrete names and numbers, paragraphs and sentences shaped like human writing, and an estimate of whether AI-text detectors would flag it (package aitext).
Package prose holds writing to the floor readers expect from a person: no stock phrases of generated copy, none of the structural habits current models have (correcting claims nobody made, announcing instead of saying, recap endings, label-colon bullets, pet words), concrete names and numbers, paragraphs and sentences shaped like human writing, and an estimate of whether AI-text detectors would flag it (package aitext).
research
Package research reads and checks the research files of a newsletter project: verbatim voices of readers and writers, the peer newsletters of the niche, and evidence from standards and studies.
Package research reads and checks the research files of a newsletter project: verbatim voices of readers and writers, the peer newsletters of the niche, and evidence from standards and studies.
skills
Package skills holds the Agent Skills (agentskills.io) that teach an agent to run lfr: setting up the server, researching and conceiving a newsletter, launching it, writing and sending each issue, learning from the numbers, and handling mailboxes.
Package skills holds the Agent Skills (agentskills.io) that teach an agent to run lfr: setting up the server, researching and conceiving a newsletter, launching it, writing and sending each issue, learning from the numbers, and handling mailboxes.
smtptest
Package smtptest runs a recording SMTP server for tests: STARTTLS or implicit TLS with a throwaway certificate, optional AUTH PLAIN, and scripted rejections.
Package smtptest runs a recording SMTP server for tests: STARTTLS or implicit TLS with a throwaway certificate, optional AUTH PLAIN, and scripted rejections.
spam
Package spam scores arriving mail.
Package spam scores arriving mail.
store
Package store owns the SQLite database: opening it with the pragmas a long-running server needs, schema migrations, transactions and the key-value settings table.
Package store owns the SQLite database: opening it with the pragmas a long-running server needs, schema migrations, transactions and the key-value settings table.
token
Package token makes and checks the signed tokens in links and addresses: confirm, unsubscribe, open, click and bounce.
Package token makes and checks the signed tokens in links and addresses: confirm, unsubscribe, open, click and bounce.
web
Package web serves everything lfr shows to readers: each list's small site (latest issues, archive, feed, signup form), the confirm and unsubscribe pages, click and open tracking, MTA-STS policies and relay webhooks.
Package web serves everything lfr shows to readers: each list's small site (latest issues, archive, feed, signup form), the confirm and unsubscribe pages, click and open tracking, MTA-STS policies and relay webhooks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL