Accounts¶
[account]
name = "personal"
maildir = "personal" # reserved; the store folder is
# the account name for now
archive = "archive" # where `a` files mail, as the
# folder appears in the sidebar;
# this is also the default
trash = "trash" # where `d` moves mail; also the
# default
[folder_names] # sidebar aliases; typed folder
# names accept either side
"inbox/accounts" = "accounts"
[imap]
host = "imap.example.com"
port = 993 # optional, defaults to 993
user = "you@example.com"
password_cmd = "pass show mail/personal"
[smtp]
host = "smtp.example.com"
port = 587 # optional
user = "you@example.com" # optional
password_cmd = "..." # optional
[[identity]]
address = "you@example.com"
name = "Your Name" # optional display name
signature = "personal" # optional; a file in signatures/
match = [
"you@example.com", # literal
"you+*@example.com", # plus-addressing
"*@you.example.com", # catch-all domain
]
pgp_sign = false # sign mail from this identity
# optional; the full fingerprint (gpg --fingerprint shows it),
# otherwise the key is picked by the identity address
pgp_key = "8F0EA48BF8BE9D3B9E1B2B9C6E5F0D3A1C2B4D5E"
[[rules]]
match_list = "~lists/somewhere" # or match_sender
move_to = "lists/somewhere" # or tag
[oauth]
provider = "google" # google | microsoft
client_id = "..." # optional
tenant = "..." # optional; a single-tenant
# Microsoft registration, see below
[graph] # Microsoft Graph sending; the
send = false # full shape is described below
Identity match patterns allow one *, only in the local
part; antiphon doctor validates every pattern and names any
bad one. On reply, the most specific match wins (literal, then
plus-pattern, then catch-all) and the From is the delivered
address verbatim.
The [[identity]] blocks are also managed in the client:
editing an account in the settings view opens an identities
list with a per-identity editor for every key above,
signature included. See
the settings view.
Accounts themselves are ranked by [accounts] order in
config.toml: listed names come first, in that order, and
the first is the primary account, selected on startup and
sending a fresh unified-view compose. See
configuration.
Folder aliases¶
[folder_names] gives folders friendlier sidebar names; the
alias also works wherever a folder name is typed (:move,
for one). The left side is the folder path as the store knows
it: lowercase, /-separated.
[folder_names]
"inbox/accounts" = "accounts"
"lists/rust-users" = "rust"
The archive and trash folders¶
a files mail to the account's archive folder and d
moves it to trash, unless the account names others:
[account]
archive = "archive"
trash = "trash"
Both are folder paths as the sidebar shows them. Deleting inside the trash folder is the only permanent delete, and it asks first.
Folder order, hidden and unsynced folders¶
Three top-level lists shape what the account's folders do.
Top-level means exactly that: they sit above the first
[table] line of the account file:
folder_order = ["inbox", "lists/aerc"]
folders_hidden = ["spam"]
folders_unsynced = ["archive/2019"]
[account]
name = "personal"
# ...
folder_orderlists sidebar folder names (as the sidebar shows them,inboxincluded) to place first, in that order; the rest stay alphabetical.folders_hiddendrops folders from the sidebar. A hidden folder is still synced and still searchable; it just is not listed.folders_unsyncedlists folders the daemon never downloads at all, matched case-insensitively on the maildir-relative name. A trailing*covers the named folder and everything beneath it:calendar*matchescalendarandcalendar/birthdaysalike. The inbox can never be excluded.
All three are edited live on the settings
Folders tab: Shift-J/K reorder, h
hides and unhides, u stops and resumes syncing.
A newly added Microsoft 365 account starts with the
mailbox's non-mail folders unsynced: calendar*,
contacts, conversation history, journal, rss feeds
and outbox. Microsoft exposes these over IMAP, and syncing
them wastes time and clutters the sidebar. They stay listed
on the Folders tab; U re-includes any you do want.
OAuth accounts (Microsoft 365, Google)¶
An OAuth account (Microsoft 365, Google Workspace) is set
up either in the settings view's account form, whose
provider row swaps the password fields for the OAuth ones
(see the settings view), or by hand
with the [oauth] table shown above; OAuth accounts need no
password_cmd. Then sign in: O on the account in the
settings Accounts tab, or antiphon oauth login <account>
on the command line. Grant state lives on the same tab, and
antiphon oauth status <account> prints it too.
The sign-in is the PKCE flow, no client secret involved.
From inside the client both providers use the browser: the
consent page opens, and a loopback listener catches the
redirect. A Microsoft sign-in falls back to the device code
(open a URL anywhere, type the code) when the listener cannot
bind. On the command line, Google uses the same loopback flow
and Microsoft the device code. The loopback flow needs the
Microsoft app registration to carry a Mobile and desktop
applications redirect of http://localhost; Entra matches
that on any port, whereas http://127.0.0.1 would have to
name the exact port. The device-code flow works without it.
The sign-in cannot land on the wrong mailbox. A Microsoft browser session for another account would otherwise satisfy the shared consent page silently, so Antiphon always shows the provider's account picker with the account's own address pre-selected, and it verifies the identity the token came back for: a sign-in that authorised a different account is rejected with an error naming both addresses, and nothing is stored.
The [oauth] client_id names your app registration with the
provider. A Microsoft account may leave it out entirely:
Antiphon then signs in with Thunderbird's public client id,
which most tenants already permit. A Google account always
needs its own registration. Tenants that restrict user
consent may still need an Entra admin to grant the app
consent once (the registration's "Grant admin consent"
button); until then the sign-in fails with the provider's
consent error.
Two environment variables override the [oauth] client_id,
useful when one registration serves every account or the id
should stay out of config files: ANTIPHON_MS_CLIENT_ID for
Microsoft accounts and ANTIPHON_GOOGLE_CLIENT_ID for
Google ones. When set, they take precedence over the account
file.
Single-tenant Microsoft registrations¶
A Microsoft app registration set to a single tenant (rather
than "any organizational directory") refuses the shared
/common sign-in endpoint:
AADSTS50194: Application '...' is not configured as a
multi-tenant application. Usage of the /common endpoint is
not supported.
Name the tenant under [oauth] and every grant, the IMAP
sign-in included, uses it in place of /common:
[oauth]
provider = "microsoft"
client_id = "..."
tenant = "contoso.onmicrosoft.com" # tenant id or domain
The [oauth] tenant covers the whole account. When only the
[graph] table sets a tenant, that one is borrowed for the
mail sign-in too, so a single value serves both; a tenant
under [graph] still overrides it for the Graph send grant.
Sending through Microsoft Graph¶
Where a Microsoft 365 tenant disallows SMTP submission, the
[graph] table routes an account's outgoing mail through
the Graph API instead:
[graph]
send = true
tenant = "00000000-0000-0000-0000-000000000000" # optional
# for delegated; required for
# app_only
client_id = "..." # optional; falls back to the
# [oauth] client_id
auth = "delegated" # delegated | app_only
secret_cmd = "pass show m365/secret" # app_only only
auth = "delegated" (the default) sends as the signed-in
user: antiphon oauth login acquires a second, Graph-scoped
grant alongside the mail one, honouring the tenant (falling
back to the [oauth] tenant, then the multi-tenant
/common/ endpoint) and a Graph-specific client_id where
the sending registration differs from the mail one.
auth = "app_only" sends with application credentials
instead: no interactive grant at all, and oauth login
skips the Graph sign-in for such accounts. The daemon
fetches a client-credentials token at send time and submits
through /users/<sender>/sendMail; tenant, a client_id
(in [graph] or [oauth]) and secret_cmd, a command that
prints the app registration's client secret, are all
required. The secret is never stored, in the vault or
anywhere else; it is asked for at the moment of use, the
same contract as password_cmd.