Help us shape the future of Go! Take the 2026 Go Developer Survey

perch

module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT-0

README

perch

Run your own IMAP and SMTP mail server on a homelab, NAS, Raspberry Pi, or spare PC with virtually unlimited mailbox storage, no public IP, no port forwarding or tunnels, and none of the usual mail server complexity.

perch is a tiny, single-binary mailbox server backed by mxRaven. Because mxRaven handles inbound MX, outbound delivery, SPF/DKIM/DMARC, IP reputation, and spam filtering at the edge, your hardware only needs outbound internet access and disk space (or an S3 bucket).

  • No public IP, port forwarding, or tunnels required. Inbound mail is pulled over an outbound WebSocket stream from mxRaven, and outbound mail is relayed over standard authenticated submission. Your server can sit behind residential NAT or CGNAT.
  • Virtually unlimited mailbox size. Store messages locally in SQLite (zstd-compressed and deduplicated) on cheap local drives, or offload raw .eml files to S3/R2 while keeping fast metadata and full-text search local.
  • Runs on old or minimal hardware. Single static Go binary (CGO-free) with embedded SQLite and FTS5 full-text search.
  • Zero MTA maintenance. No Postfix, Dovecot, Rspamd, DKIM signing daemons, or IP warm-up to manage. Moving messages to or from Junk automatically trains mxRaven's spam filter.
  • Run locally or remotely. Bind to 127.0.0.1 or your LAN on a homelab, or expose it on a VPS with built-in Let's Encrypt (ACME) TLS.

Install

Prebuilt binaries for Linux, macOS, and Windows are published on the releases page. Every release includes SHA-256 checksums, SBOMs, a keyless cosign signature, and GitHub build provenance; see RELEASING.md.

With Go

If you have Go 1.26.4 or later, go install builds the latest tagged release (CGO is not required):

go install github.com/synqronlabs/perch/cmd/perch@latest

The binary is written to $(go env GOPATH)/bin (usually ~/go/bin); add that directory to your PATH if it is not already there. To pin a specific version:

go install github.com/synqronlabs/perch/cmd/perch@v0.1.0
From the latest GitHub release

This downloads the newest published release for your OS and architecture, installs it to ~/.local/bin, and works without a Go toolchain:

# Resolve the latest release tag (for example v0.1.0).
version=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
  https://github.com/synqronlabs/perch/releases/latest | sed 's#.*/tag/v##')
[ -n "$version" ] || { echo "could not determine the latest release" >&2; exit 1; }

# Pick the matching archive for this machine.
os=$(uname -s | tr '[:upper:]' '[:lower:]')
case $(uname -m) in
  x86_64) arch=amd64 ;;
  aarch64 | arm64) arch=arm64 ;;
  armv6l) arch=armv6 ;;
  armv7l) arch=armv7 ;;
  *) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac

archive="perch_${version}_${os}_${arch}.tar.gz"
curl -fsSLO "https://github.com/synqronlabs/perch/releases/download/v${version}/${archive}"
tar -xzf "${archive}"
mkdir -p ~/.local/bin
install -m 0755 "perch_${version}_${os}_${arch}/perch" ~/.local/bin/perch

Make sure ~/.local/bin is on your PATH, then check the install:

perch version

To install system-wide instead, replace the last two lines with:

sudo install -m 0755 "perch_${version}_${os}_${arch}/perch" /usr/local/bin/perch

Windows users can download the .zip archive for their architecture from the releases page.

From source

Requires Go 1.26.4 or later (CGO-free build):

CGO_ENABLED=0 go build -trimpath -o perch ./cmd/perch

For a long-running Linux deployment, see Run as a systemd service, including optional timers for backups and garbage collection.

Quickstart (Homelab / Local Hardware)

1. Provision mxRaven and generate config

With an mxRaven API token, perch setup automatically configures your domain, inbound route, submission listener, spam/auth rules, and writes perch.yaml:

export MXRAVEN_API_TOKEN=...

./perch setup \
  --config ~/.local/share/perch/perch.yaml \
  --data-dir ~/.local/share/perch \
  --workspace my-workspace \
  --domain example.com \
  --hostname localhost

Publish the DNS records printed by the wizard (or pass --wait to wait until DNS verifies).

2. Create an account and start the server
./perch account add --config ~/.local/share/perch/perch.yaml --email you@example.com
./perch serve --config ~/.local/share/perch/perch.yaml

Want to test locally before connecting mxRaven? Run ./perch init --data-dir ~/.local/share/perch --hostname localhost --email you@example.com instead.

3. Connect your mail client

Point Thunderbird, Apple Mail, or any IMAP client to:

Setting Value
IMAP 127.0.0.1:1143 (plaintext on loopback)
SMTP 127.0.0.1:1587 (plaintext on loopback)
Username you@example.com
Password your account password

To connect from other devices on your LAN, generate a self-signed certificate with perch init --self-signed (IMAPS on 1993, SMTPS on 1465) or provide your own TLS certificate via --tls-cert and --tls-key. Off-loopback connections always require TLS.

Archival with S3

If you want bottomless cloud storage, enable S3 mode. mxRaven writes incoming .eml files directly to your bucket via S3_STORE, and perch reconciles the bucket automatically:

export MXRAVEN_API_TOKEN=...
export PERCH_S3_SECRET_KEY=...

./perch setup \
  --config ~/.local/share/perch/perch.yaml \
  --data-dir ~/.local/share/perch \
  --workspace my-workspace \
  --domain example.com \
  --hostname mail.example.com \
  --storage s3 \
  --s3-bucket my-mail-archive \
  --s3-region auto \
  --s3-endpoint https://<account>.r2.cloudflarestorage.com \
  --s3-access-key <access-key>

To expose perch directly on a public VPS with standard ports (143/993/587/465) and automatic Let's Encrypt certificates, add:

  --public \
  --acme-email admin@example.com \
  --acme-domain mail.example.com

Configuration

perch init and perch setup write YAML with 0600 permissions. Important sections:

data_dir: /var/lib/perch
hostname: mail.example.com

imap:
  addr: 127.0.0.1:1143
  imaps_addr: ""
  allow_plaintext_loopback: true

submission:
  addr: 127.0.0.1:1587
  tls_addr: ""

tls:
  cert_file: ""
  key_file: ""
  acme:
    enabled: false
    email: ""
    domains: []
    http_addr: ":80"

storage:
  mode: sqlite            # sqlite | s3
  s3:
    endpoint: ""
    region: ""
    bucket: ""
    prefix: "perch/"
    access_key: ""
    secret_key_env: PERCH_S3_SECRET_KEY
    force_path_style: false
    key_template: "perch/{{.mailbox.address}}/{{.task.id}}.eml"
    reconcile_every: 5m
    spool: false
    spool_max_bytes: 268435456

limits:
  max_connections: 512
  ingest_concurrency: 2
  blob_cache_bytes: 67108864
  max_message_bytes: 26214400
  max_recipients: 100

mxraven:
  submission_host: smtp.mxraven.email
  submission_port: 587
  notification_url: wss://notifications.mxraven.com
  feedback_url: https://api.mxraven.com
  listeners:
    - id: ...
      kind: mta
      key_prefix: ...
      key_secret_env: PERCH_MXRAVEN_KEY_SECRET
      consumer: notify-...
    - id: ...
      kind: submission
      key_prefix: ...
      key_secret_env: PERCH_MXRAVEN_SUBMISSION_KEY_SECRET

Secrets can be inline (protected by file permissions) or referenced through environment variables. The control-plane token is used only by perch setup and is never written to the config.

Run as a systemd service (Linux)

perch serve runs in the foreground, logs to stderr, drains connections on SIGTERM/SIGINT (waiting up to five seconds), and reloads certificate files on SIGHUP, so it maps directly onto a Type=simple unit. It does not fork, write a PID file, implement sd_notify, or support socket activation, so do not use Type=notify or socket units.

The instructions below use a dedicated perch service account, /etc/perch/perch.yaml for configuration, and /var/lib/perch for data. Adjust the paths if your layout differs, and keep data_dir in the YAML in sync with the unit's StateDirectory and ReadWritePaths.

The built-in default config path comes from os.UserConfigDir() ($XDG_CONFIG_HOME/perch/perch.yaml, usually ~/.config/perch/perch.yaml). Always pass --config explicitly in service units rather than relying on $HOME.

1. Create the service account and install
sudo useradd --system --home-dir /var/lib/perch --shell /usr/sbin/nologin perch
sudo install -Dm0755 perch /usr/local/bin/perch
sudo install -d -m 0700 -o perch -g perch /etc/perch /var/lib/perch

# Provision mxRaven and write the config as the service user.
sudo -u perch -H env MXRAVEN_API_TOKEN=... \
  /usr/local/bin/perch setup \
  --config /etc/perch/perch.yaml \
  --data-dir /var/lib/perch \
  --workspace my-workspace \
  --domain example.com \
  --hostname mail.example.com

# Create the first mailbox.
sudo -u perch -H /usr/local/bin/perch account add \
  --config /etc/perch/perch.yaml \
  --email you@example.com

Run perch setup, perch init, perch account, and perch doctor as the service user (or chown their output afterwards). perch writes the config with mode 0600 and the data directory with mode 0700, so a root-owned config will not be readable by the perch account.

Verify everything before starting the service:

sudo -u perch -H /usr/local/bin/perch doctor --config /etc/perch/perch.yaml
2. Create the unit

Write /etc/systemd/system/perch.service:

[Unit]
Description=perch mail server
Documentation=https://github.com/synqronlabs/perch
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=perch
Group=perch
ExecStart=/usr/local/bin/perch serve --config /etc/perch/perch.yaml
ExecReload=/bin/kill -HUP $MAINPID
EnvironmentFile=-/etc/perch/perch.env
Restart=on-failure
RestartSec=5s
TimeoutStopSec=15s
LimitNOFILE=65536
UMask=0077

# Must match data_dir in /etc/perch/perch.yaml.
StateDirectory=perch
StateDirectoryMode=0700
ReadWritePaths=/var/lib/perch

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

If you use perch setup --public (ports 143/993/587/465) or ACME (the HTTP-01 listener binds :80), add these two lines to the [Service] section so the unprivileged service user can bind privileged ports:

AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

Open the corresponding ports in your firewall; for a public deployment:

sudo ufw allow 80/tcp
sudo ufw allow 143,993,587,465/tcp

The default quickstart ports (1143/1587/1993/1465) are unprivileged and need neither capabilities nor firewall changes for loopback use.

ProtectSystem=strict and ProtectHome=true assume the config lives under /etc and the data directory under /var/lib. If you keep either under a home directory, relax those options or add the necessary ReadWritePaths. The ACME cache defaults to <data_dir>/acme, so it is writable via StateDirectory; add it to ReadWritePaths if you set tls.acme.cache_dir elsewhere.

ExecReload sends SIGHUP, which perch only handles when tls.cert_file and tls.key_file are configured. With ACME or a plaintext-only loopback setup, remove the ExecReload line: SIGHUP would terminate the process.

3. Optional: secrets from the environment

perch setup writes listener and S3 secrets inline into the 0600 config by default. To keep them out of the YAML, replace key_secret: with key_secret_env: PERCH_MXRAVEN_KEY_SECRET (and similarly for the submission listener) and secret_key: with secret_key_env: PERCH_S3_SECRET_KEY, then put the values in /etc/perch/perch.env:

sudo install -m 0600 /dev/null /etc/perch/perch.env
sudo tee /etc/perch/perch.env >/dev/null <<'EOF'
PERCH_S3_SECRET_KEY=...
PERCH_MXRAVEN_KEY_SECRET=...
PERCH_MXRAVEN_SUBMISSION_KEY_SECRET=...
EOF

systemd reads EnvironmentFile as root before dropping privileges, so the file can remain root-owned and unreadable by the service account.

4. Enable and verify
sudo systemctl daemon-reload
sudo systemctl enable --now perch
sudo systemctl status perch
sudo journalctl -u perch -f

Logs are captured by journald; set log.format: json in perch.yaml for structured output. Account, alias, and mailbox changes are written to SQLite and apply immediately without a restart. After changing perch.yaml, run sudo systemctl restart perch. When tls.cert_file and tls.key_file are configured, manual certificates reload without dropping connections via sudo systemctl reload perch (SIGHUP); ACME renews and reloads on its own and needs no reload command.

5. systemd timers for maintenance

perch gc and perch backup are one-shot commands and are safe to run while perch serve is running (SQLite WAL mode allows concurrent readers and writers). perch reconcile does not need a timer: serve reconciles S3 at startup and every storage.s3.reconcile_every (default 5m).

Backup timer

perch backup refuses to overwrite an existing destination, so use a small wrapper to add timestamps and prune old copies (adjust -mtime +14 to match your retention policy). Write /usr/local/bin/perch-backup:

sudo tee /usr/local/bin/perch-backup >/dev/null <<'EOF'
#!/bin/sh
set -eu
stamp=$(date +%Y%m%d-%H%M%S)
/usr/local/bin/perch backup --config /etc/perch/perch.yaml "/var/lib/perch/backups/perch-$stamp.db"
find /var/lib/perch/backups -type f -name 'perch-*.db' -mtime +14 -delete
EOF
sudo chmod 0755 /usr/local/bin/perch-backup

Write /etc/systemd/system/perch-backup.service:

[Unit]
Description=perch SQLite backup
After=perch.service

[Service]
Type=oneshot
User=perch
Group=perch
ExecStart=/usr/local/bin/perch-backup
EnvironmentFile=-/etc/perch/perch.env
UMask=0077
Nice=10
IOSchedulingClass=idle

Write /etc/systemd/system/perch-backup.timer:

[Unit]
Description=Daily perch backup

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15m
Persistent=true
Unit=perch-backup.service

[Install]
WantedBy=timers.target
Garbage collection timer

perch gc removes orphaned local blobs, dead letters older than 30 days (--dead-letter-age), and enforces the S3 spool limit, then runs an incremental vacuum. Add --s3-delete if you also want it to delete S3 objects belonging to deleted messages; without it, those tombstones accumulate.

Write /etc/systemd/system/perch-gc.service:

[Unit]
Description=perch garbage collection
After=perch.service

[Service]
Type=oneshot
User=perch
Group=perch
ExecStart=/usr/local/bin/perch gc --config /etc/perch/perch.yaml
EnvironmentFile=-/etc/perch/perch.env
UMask=0077
Nice=10
IOSchedulingClass=idle

Write /etc/systemd/system/perch-gc.timer:

[Unit]
Description=Daily perch garbage collection

[Timer]
OnCalendar=*-*-* 04:30:00
RandomizedDelaySec=15m
Persistent=true
Unit=perch-gc.service

[Install]
WantedBy=timers.target
Enable the timers
sudo systemctl daemon-reload
sudo systemctl enable --now perch-backup.timer perch-gc.timer
systemctl list-timers --all 'perch-*'

Run a job immediately with sudo systemctl start perch-backup.service or sudo systemctl start perch-gc.service; inspect results with journalctl -u perch-backup.service -u perch-gc.service.

The timers run as perch and only need the same config and environment as the main service. If storage.mode is sqlite, --s3-delete and the spool steps are ignored. To take an on-demand backup, run the wrapper or sudo -u perch -H /usr/local/bin/perch backup --config /etc/perch/perch.yaml /path/to/copy.db (the destination must not already exist, and perch must be able to write to its directory).

6. Optional: rootless user service

perch also works as a per-user service under systemctl --user. Put the unit in ~/.config/systemd/user/, drop User=, Group=, and StateDirectory=, adjust the ProtectHome/ProtectSystem/ReadWritePaths options for your home layout, and pass explicit --config/--data-dir paths. Enable lingering so the service survives logout:

loginctl enable-linger "$USER"
systemctl --user enable --now perch

User services cannot acquire CAP_NET_BIND_SERVICE, so this only works with unprivileged listener ports (the quickstart defaults, or custom ports above 1024). Use the system-level unit for --public or ACME.

CLI Reference

perch init        create a local configuration and optional first account
perch setup       provision mxRaven domain, listeners, keys, routes, and S3 rule
perch serve       run IMAP, submission, and the mxRaven notification consumer
perch account     add | list | passwd | disable | enable
perch alias       add | remove | list
perch mailbox     list
perch doctor      verify SQLite, FTS5, TLS, DNS, S3, and mxRaven connectivity
perch reconcile   force an immediate S3 bucket scan
perch gc          clean up orphaned local blobs and enforce S3 spool limits
perch backup      write a consistent SQLite backup
perch version     print version

Features at a Glance

  • Standard IMAP4rev1: IDLE, UIDPLUS, MOVE, NAMESPACE, SPECIAL-USE, LIST-EXTENDED, LIST-STATUS, STATUS=SIZE, ESEARCH, LITERAL+, SASL-IR, APPENDLIMIT.
  • Full-text search: SQLite FTS5 across subject, addresses, headers, body, and attachment names.
  • Aliases & catch-all: Per-account aliases, user+tag@domain plus-addressing, and catch-all domains.
  • Inbound policy: Configurable SPF/DMARC rejection, malware rejection, Rspamd routing to Junk, and listener rate limits via perch setup flags.
  • Secure: Argon2id password hashes, TLS required off-loopback, authenticated sender enforcement, and zero inbound SMTP ports.
  • Private: mxRaven never persists your emails. All your emails remain with you on your S3 bucket or local disk.

License

Released under the MIT No Attribution (MIT-0) license. See LICENSE.

Directories

Path Synopsis
cmd
perch command
Command perch runs a small IMAP mailbox service backed by mxRaven.
Command perch runs a small IMAP mailbox service backed by mxRaven.
internal
authlimit
Package authlimit provides a small in-memory login failure limiter.
Package authlimit provides a small in-memory login failure limiter.
blob
Package blob reads raw messages from S3-compatible object storage.
Package blob reads raw messages from S3-compatible object storage.
blobcache
Package blobcache provides a bounded in-memory LRU for raw message bodies.
Package blobcache provides a bounded in-memory LRU for raw message bodies.
cli
Package cli implements the perch command line.
Package cli implements the perch command line.
config
Package config loads and validates the perch configuration file.
Package config loads and validates the perch configuration file.
hub
Package hub fans out in-process mailbox change signals to IMAP sessions.
Package hub fans out in-process mailbox change signals to IMAP sessions.
imapd
Package imapd implements perch's IMAP server on top of go-imap/v2.
Package imapd implements perch's IMAP server on top of go-imap/v2.
ingest
Package ingest consumes mxRaven notification events and stores inbound mail.
Package ingest consumes mxRaven notification events and stores inbound mail.
mailmsg
Package mailmsg adapts parsed messages to the storage input type.
Package mailmsg adapts parsed messages to the storage input type.
mailparse
Package mailparse extracts IMAP envelope metadata, a cached MIME tree, and full-text search text from raw RFC 5322 messages.
Package mailparse extracts IMAP envelope metadata, a cached MIME tree, and full-text search text from raw RFC 5322 messages.
netlimit
Package netlimit bounds the number of accepted network connections.
Package netlimit bounds the number of accepted network connections.
netutil
Package netutil contains small network-address helpers shared by the listeners and the command line.
Package netutil contains small network-address helpers shared by the listeners and the command line.
password
Package password hashes and verifies account passwords with Argon2id.
Package password hashes and verifies account passwords with Argon2id.
provision
Package provision creates the mxRaven domain, listeners, keys, routes, and S3 archive rule needed by a perch installation.
Package provision creates the mxRaven domain, listeners, keys, routes, and S3 archive rule needed by a perch installation.
smtpd
Package smtpd exposes authenticated submission and relays it to mxRaven with Raven's streaming SMTP client.
Package smtpd exposes authenticated submission and relays it to mxRaven with Raven's streaming SMTP client.
store
Package store owns perch's SQLite schema, blobs, messages, and search index.
Package store owns perch's SQLite schema, blobs, messages, and search index.
tlsutil
Package tlsutil builds TLS configurations for IMAP and submission.
Package tlsutil builds TLS configurations for IMAP and submission.

Jump to

Keyboard shortcuts

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