Email¶
altero can run without an SMTP relay. Configure email only if your instance needs messages delivered to users rather than written to the server log.
Do you need a mail relay?¶
| Feature | Without SMTP | With SMTP |
|---|---|---|
| Email confirmation | Link written to log | Delivered by email |
| Group invitations | Available in-app for existing accounts; link can be logged | Delivered by email |
| Administrator-issued password link | Link shown to administrator | Also delivered by email when possible |
| Self-service password reset | Unavailable | Available when enabled |
| Email second-factor code | Not useful without delivery | Delivered by email |
| Security notices | Not delivered | Delivered to confirmed addresses |
| Group activity digests | In-app notification still exists | Optional email copy |
Most messages fall back to the log when no relay is configured. Self-service password reset does not, because a password-setting link should not be exposed to anyone who can read server logs.
Messages altero sends¶
altero sends plain-text mail for:
- confirming an email address;
- group invitations;
- password-setting links;
- email second-factor codes;
- password-change notices;
- second-factor change notices; and
- opt-in group activity digests.
Security notices are sent only to a confirmed email address.
Confirmation links are valid for 24 hours. Administrator-issued or self-service password links are single-use and valid for 12 hours. Email sign-in codes are valid for ten minutes, work once and are tied to the browser session that requested them.
Run without SMTP¶
With ALTERO_SMTP_URL unset, altero logs messages at WARNING instead of sending them.
For Docker Compose, find a logged message with:
This is a supported operating mode. An account with an unconfirmed address can still sign in, synchronize and use its library.
What you lose is external delivery: security notices, useful email-code authentication, self-service password reset and invitations to people who do not yet have an account on the instance.
Configure an SMTP relay¶
Set three values:
| Setting | Environment variable | Purpose |
|---|---|---|
SMTP_URL |
ALTERO_SMTP_URL |
SMTP relay and optional credentials |
MAIL_FROM |
ALTERO_MAIL_FROM |
Sender address |
PUBLIC_URL |
ALTERO_PUBLIC_URL |
Base URL used in links |
Example:
ALTERO_SMTP_URL='smtp://altero%40example.org:s3cret@mail.example.org:587' \
ALTERO_MAIL_FROM='altero <altero@example.org>' \
ALTERO_PUBLIC_URL='https://altero.example.org' \
uv run altero
Equivalent config.py values:
SMTP_URL = "smtp://altero%40example.org:s3cret@mail.example.org:587"
MAIL_FROM = "altero <altero@example.org>"
PUBLIC_URL = "https://altero.example.org"
Invalid SMTP URLs are rejected when configuration is loaded, so configuration errors appear at startup rather than only when a user is waiting for mail.
SMTP URL format¶
smtp://defaults to port 587 and attemptsSTARTTLS.smtps://defaults to port 465 and uses TLS from the start.
With smtp://, if the relay does not offer STARTTLS, altero logs a warning and can continue in clear text. That can be acceptable for a relay on the same trusted host or container network. Over a network you do not control, prefer smtps://.
Encode credentials in the URL¶
Usernames and passwords are URL components. Characters such as @, :, /, #, ? and % must be percent-encoded.
For example:
A relay that needs no authentication can omit credentials:
Sender address¶
ALTERO_MAIL_FROM accepts either a bare address or a display name plus address.
Use an address the relay is allowed to send as. SPF, DKIM and DMARC configuration belongs to the mail provider or relay; altero only authenticates to the relay and hands it the message.
The default altero@localhost is a placeholder, not a deliverable production address.
Public URL¶
Set ALTERO_PUBLIC_URL to the external address users can open, for example:
This matters especially behind a reverse proxy. Without a public URL, generated links can use an internal host or port from the request that reached altero.
ALTERO_PUBLIC_URL is separate from ALTERO_FORWARDED_ALLOW_IPS: the first controls generated public links and callback addresses, while the second controls which forwarded client address altero trusts.
Docker Compose¶
Put the mail settings in docker/.env, which the Compose file hands to the container along with everything else in it (see Configuration):
POSTGRES_PASSWORD=something-other-than-the-default
ALTERO_SMTP_URL=smtps://altero%40example.org:s3cret@mail.example.org
ALTERO_MAIL_FROM=altero@example.org
ALTERO_PUBLIC_URL=https://altero.example.org
Then restart and inspect the resolved configuration:
docker compose -f docker/compose.yaml up -d
docker compose -f docker/compose.yaml config | grep ALTERO_
Common Docker mistakes¶
Put the file in docker/.env. The documented Compose file lives in docker/, and that is the environment file it reads.
Escape $ for Compose. In .env, Compose interpolates $NAME. Double a literal dollar sign as $$. This is in addition to URL percent-encoding.
Example password p@ss$1 inside the SMTP URL:
Remember that localhost means the container. A relay on the Docker host can be reached through host.docker.internal when the host relay is listening on an address reachable from the container network.
Group notifications¶
Group activity email is opt-in. Every member starts unsubscribed.
A user can independently subscribe to these categories for each group:
| Category | Examples |
|---|---|
| Items added or changed | New item, title edit |
| Items deleted | Trashed or permanently removed |
| People joining or leaving | Membership changes |
| Collections added or changed | Group-library organization |
Digest timing¶
Zotero uploads can contain many writes. Sending mail for every write would create a flood, so altero waits until the group has been quiet and sends one digest for the accumulated activity.
| Environment variable | Default | Meaning |
|---|---|---|
ALTERO_GROUP_DIGEST_QUIET_PERIOD |
900 |
Seconds the library must be quiet before a digest is ready |
ALTERO_GROUP_DIGEST_INTERVAL |
60 |
How often the server looks for ready digests; 0 disables delivery |
A member does not receive a digest about changes they caused themselves.
The email contains counts, not item titles. Detailed activity remains in the authenticated web interface so library contents are not unnecessarily copied into mail systems and logs.
Digest processing is coordinated in the database, so several application workers do not send the same digest twice.
There is no retry queue. If a relay refuses a digest, altero logs the failure and still keeps the in-app notification.
Check mail delivery¶
The quickest real test is an email-confirmation message:
- Sign in with an account whose address is unconfirmed.
- Use Resend in the confirmation notice.
- Check the altero log for an SMTP error.
- Check the recipient mailbox.
A successful handoff to the SMTP relay does not guarantee delivery to the final inbox.
For a local SMTP sink:
uvx --from aiosmtpd python -m aiosmtpd -n -l localhost:8025
ALTERO_SMTP_URL=smtp://localhost:8025 uv run altero
This prints received messages instead of sending real mail.
Failure behavior¶
Mail failure does not roll back the action that caused the message. For example, a password change remains a password change even if the security notice cannot be delivered.
Failures are logged. There is no mail queue and no automatic retry. Most user-triggered messages can be requested again.
Mail sending runs on a worker thread with a 10-second timeout so a slow relay does not block the server's event loop.