- Go 83.4%
- Vue 9%
- TypeScript 6.5%
- Just 0.6%
- Dockerfile 0.3%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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 |
||
| .forgejo/workflows | ||
| .idea | ||
| cmd | ||
| e2e | ||
| internal | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| CHANGELOG.md | ||
| DEVELOPMENT.md | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| README.md | ||
| SECURITY.md | ||
| SPECS.md | ||
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.host → DEADDROP_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.secretin 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.