No description
  • Go 83.4%
  • Vue 9%
  • TypeScript 6.5%
  • Just 0.6%
  • Dockerfile 0.3%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Stefan Bethke 57b43e0b9c
All checks were successful
Build and publish image / ci (push) Successful in 3m7s
Build and publish image / docker (push) Successful in 2m56s
Fix overview/mailbox list layout and add per-message expiry
Frontend: the permanent and temporary mailbox headings now render at
the same heading level, and the mailbox/message list rows fill their
container width instead of shrinking to fit their content (DaisyUI's
.menu defaults to width: fit-content, and .list-row's single-item grid
track sizes to content unless given the list-col-grow class).

Also adds each message's expiry date to the mailbox message list, the
same way temporary mailboxes already show theirs on the overview page.
This required a small backend addition: imapstore.MessageSummary now
carries InternalDate, and service.ListMailboxMessages computes each
message's expiry from it plus the mailbox's configured message TTL.

Adds the v1.5.1 changelog entry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TU5Nkn4vNkykcD9a71MLq7
2026-08-22 15:14:14 +00:00
.forgejo/workflows Reinstate the GITHUB_TOKEN override in the just-install step 2026-08-22 08:44:09 +00:00
.idea Complete the Goland config. 2026-08-22 10:14:20 +00:00
cmd Watch the config file and reload it automatically on change 2026-08-22 13:21:31 +00:00
e2e Rename SECURING_MESSAGE_DISPLAY.md to SECURITY.md, audit and fix findings 2026-08-22 10:07:37 +00:00
internal Fix overview/mailbox list layout and add per-message expiry 2026-08-22 15:14:14 +00:00
web Fix overview/mailbox list layout and add per-message expiry 2026-08-22 15:14:14 +00:00
.dockerignore Add multi-arch Docker build and Forgejo Actions publish workflow 2026-08-21 15:34:58 +00:00
.gitignore Initial implementation of Deaddrop per SPECS.md 2026-08-21 07:32:47 +00:00
CHANGELOG.md Fix overview/mailbox list layout and add per-message expiry 2026-08-22 15:14:14 +00:00
DEVELOPMENT.md Rename SECURING_MESSAGE_DISPLAY.md to SECURITY.md, audit and fix findings 2026-08-22 10:07:37 +00:00
Dockerfile Add multi-arch Docker build and Forgejo Actions publish workflow 2026-08-21 15:34:58 +00:00
go.mod Rename SECURING_MESSAGE_DISPLAY.md to SECURITY.md, audit and fix findings 2026-08-22 10:07:37 +00:00
go.sum Add a per-mailbox WebSocket for live message updates 2026-08-21 10:43:58 +00:00
justfile Rename SECURING_MESSAGE_DISPLAY.md to SECURITY.md, audit and fix findings 2026-08-22 10:07:37 +00:00
README.md Watch the config file and reload it automatically on change 2026-08-22 13:21:31 +00:00
SECURITY.md Fall back through more headers to find a message's real destination 2026-08-22 14:47:15 +00:00
SPECS.md Fall back through more headers to find a message's real destination 2026-08-22 14:47:15 +00:00

Deaddrop

Deaddrop makes email delivered to virtual inboxes on top of an IMAP mailbox available over a REST API and web frontend for a limited time. See SPECS.md for the full theory of operation and SECURITY.md for its security model.

Run

bin/deaddrop -c deaddrop.toml
# or
bin/deaddrop --config deaddrop.toml --log-level debug

Deaddrop is expected to run behind a TLS-terminating reverse proxy. The proxy must set (overwriting, not merging with, any client-supplied value) the X-Forwarded-Proto header, since Deaddrop trusts it to decide whether session/OAuth-state cookies get the Secure flag — see SECURITY.md. Don't expose Deaddrop directly to untrusted clients without such a proxy in front. See DEVELOPMENT.md to build bin/deaddrop from source.

Configuration

Configuration comes from a TOML file (-c/--config) and/or DEADDROP_* environment variables (e.g. imap.hostDEADDROP_IMAP_HOST; dots become underscores, and the whole key is upper-cased). Both are optional to varying degrees: a config file isn't required at all if every setting is supplied via environment variables, and any key omitted from both falls back to its default (or fails startup if it's required and has no default).

Precedence: environment variables override the config file, which overrides the built-in defaults below.

Unknown keys: a key that doesn't match anything in the tables below (usually a typo, e.g. address_pattern instead of address_patterns) logs a startup warning naming the offending key rather than silently doing nothing — check the logs if a setting doesn't seem to be taking effect.

Durations (poll_interval, max_inbox_lifetime, etc.) accept Go's stdlib units (ns, us/µs, ms, s, m, h) plus d for days, and can combine them, e.g. "36h", "1d12h", "14d".

Configuration reload

When a config file is given (-c/--config) and general.auto_reload is true (the default), Deaddrop polls it for a modification-time change every few seconds and reloads it automatically — no restart or signal needed. If the file fails to parse or fails validation, the error is logged and whatever was already running keeps running unaffected, so a syntax error mid-edit can't take down the server.

Not every setting can be applied to an already-running process just by reloading the file, though:

  • Live-reloadable: imap.host, imap.port, imap.username, imap.password, imap.mailbox, imap.security, imap.poll_interval (applied by reconnecting to the IMAP server — but only when one of these actually changed, so editing something else doesn't interrupt an otherwise-healthy connection), imap.address_patterns, imap.expunge_interval, general.max_inbox_lifetime, general.max_message_lifetime, http.base_url, and [[permanent_addresses]] (added, removed, relabeled, or re-lifetimed entries all take effect; each address's token stays derived from the original startup secret — see below — so it never changes under an existing link).
  • Restart-only: general.secret, http.listen_addr, and the entire [oidc] section. A reload that changes one of these logs a warning instead of applying it. general.secret in particular must never be rotated by editing a live config file: doing so would make every existing virtual inbox's control message fail signature verification and be deleted on the next reconciliation pass, and would invalidate every logged-in session — restart the process instead, deliberately, when you actually intend to rotate it.

Set general.auto_reload = false to disable this and only ever pick up config changes on a restart.

Command-line flags

Flag Default Description
-c, --config (none) Path to the config file. If omitted, configuration comes entirely from environment variables and defaults.
-l, --log-level info One of debug, info, warn, error.

[general]

Key Env var Default Description
secret DEADDROP_GENERAL_SECRET (required) Used to HMAC-sign the mailbox-config control messages Deaddrop stores in the IMAP mailbox, and to derive session-cookie/OAuth-state keys. Keep it stable across restarts — rotating it invalidates every logged-in session, and, more importantly, makes every existing virtual inbox's control message fail signature verification, which causes Deaddrop to delete them (and their mail) on the next reconciliation pass.
max_inbox_lifetime DEADDROP_GENERAL_MAX_INBOX_LIFETIME 168h (7 days) Maximum lifetime a caller may request for a temporary virtual inbox; also the default when they don't specify one.
max_message_lifetime DEADDROP_GENERAL_MAX_MESSAGE_LIFETIME 168h (7 days) Maximum time a message is kept in an inbox before being deleted; also the default when a caller doesn't specify one.
auto_reload DEADDROP_GENERAL_AUTO_RELOAD true Whether Deaddrop watches the config file (-c/--config) and reloads it automatically when it changes. See "Configuration reload" below for what actually takes effect live.

[imap]

Connection to, and management of, the backing IMAP mailbox.

Key Env var Default Description
host DEADDROP_IMAP_HOST (required) IMAP server hostname.
port DEADDROP_IMAP_PORT 993 IMAP server port.
username DEADDROP_IMAP_USERNAME (required) IMAP login username.
password DEADDROP_IMAP_PASSWORD (none) IMAP login password.
mailbox DEADDROP_IMAP_MAILBOX INBOX The IMAP mailbox (folder) Deaddrop manages.
security DEADDROP_IMAP_SECURITY tls One of tls (implicit TLS, typically port 993), starttls (plaintext upgraded via the STARTTLS command, typically port 143), or none (no TLS at all — only for trusted local/test setups).
address_patterns DEADDROP_IMAP_ADDRESS_PATTERNS (required, at least one) Glob patterns (* = any run of characters) for the addresses Deaddrop accepts mail for and allows users to request, e.g. ["*@drop.example.com", "temp-*@example.com"]. Mail to any other address is deleted immediately. As an env var, separate multiple patterns with commas: *@drop.example.com,temp-*@example.com.
poll_interval DEADDROP_IMAP_POLL_INTERVAL 1m How often to poll the mailbox for changes, in addition to IMAP IDLE (used automatically when the server supports it).
expunge_interval DEADDROP_IMAP_EXPUNGE_INTERVAL 5m How often messages marked deleted are permanently expunged from the mailbox.

[http]

Key Env var Default Description
listen_addr DEADDROP_HTTP_LISTEN_ADDR :8080 Address:port the HTTP server listens on. Deaddrop is expected to run behind a TLS-terminating reverse proxy, so this is normally plain HTTP.
base_url DEADDROP_HTTP_BASE_URL (required) The externally visible base URL (as seen through the reverse proxy), used to build the mailbox links returned by the API, e.g. https://drop.example.com.

[oidc] (optional)

The entire section is optional. If issuer_url is empty (the section is omitted entirely, or just that key is), OIDC is considered unconfigured: no login is offered, and no authorization checks are performed at all — every API endpoint behaves as if the caller were logged in.

Key Env var Default Description
issuer_url DEADDROP_OIDC_ISSUER_URL (none) The IdP's OIDC issuer URL, used for discovery (<issuer_url>/.well-known/openid-configuration). Setting this is what enables OIDC.
client_id DEADDROP_OIDC_CLIENT_ID (none) OAuth2 client ID registered with the IdP.
client_secret DEADDROP_OIDC_CLIENT_SECRET (none) OAuth2 client secret registered with the IdP.
redirect_url DEADDROP_OIDC_REDIRECT_URL (none) Callback URL registered with the IdP; conventionally <base_url>/v1/auth/callback.

[[permanent_addresses]] (optional)

Permanent addresses are configured here — this is the only way to set one up; POST /v1/address always creates a temporary address, even if a client sends "permanent": true. Each entry's access token is derived deterministically from its address and general.secret (not stored anywhere), so it stays stable across restarts; find it via GET /v1/address/.

Key Env var Default Description
address DEADDROP_PERMANENT_ADDRESSES_<n>_ADDRESS (required) The address this permanent inbox catches mail for.
label DEADDROP_PERMANENT_ADDRESSES_<n>_LABEL (none) Optional friendly name shown on the home page.
message_lifetime DEADDROP_PERMANENT_ADDRESSES_<n>_MESSAGE_LIFETIME general.max_message_lifetime Overrides how long messages in this inbox are kept before being deleted.

As a TOML array of tables, this has no single flat env var; instead, each entry gets its own index <n> starting at 0 (indices need not be contiguous, just distinct — they're only used for ordering), e.g. DEADDROP_PERMANENT_ADDRESSES_0_ADDRESS, DEADDROP_PERMANENT_ADDRESSES_1_ADDRESS. As with every other setting, if any DEADDROP_PERMANENT_ADDRESSES_* env vars are set, they replace the config file's [[permanent_addresses]] list entirely rather than merging with it.

[[permanent_addresses]]
address = "support@drop.example.com"
label = "Support"

[[permanent_addresses]]
address = "info@drop.example.com"
message_lifetime = "30d"

Full example

[general]
secret = "change-me"
max_inbox_lifetime = "7d"
max_message_lifetime = "7d"

[imap]
host = "imap.example.com"
port = 993
username = "deaddrop"
password = "hunter2"
mailbox = "INBOX"
security = "tls"
address_patterns = ["*@drop.example.com", "temp-*@example.com"]
poll_interval = "1m"
expunge_interval = "5m"

[http]
listen_addr = ":8080"
base_url = "https://drop.example.com"

[oidc]
issuer_url = "https://idp.example.com"
client_id = "deaddrop"
client_secret = "oidc-secret"
redirect_url = "https://drop.example.com/v1/auth/callback"

[[permanent_addresses]]
address = "support@drop.example.com"
label = "Support"

Development

See DEVELOPMENT.md for building from source, local development (hot-reload, a throwaway test mailbox), running the test suite, and the REST API reference.