Repository navigation
Releases
Releases · dominik-lenhart/dove
dove v0.12.1
Changed
- Clearer descriptions for
list_accounts,list_labels,get_thread,get_draft,list_drafts,list_watches,
delete_watch,list_requestsandcancel_request: each says when to use it instead of a neighbouring tool, what
it returns when an id is unknown, and (forget_thread) how a result cut by the size budget is marked. - The other tool descriptions now name the neighbouring tool to use instead (for example
move_messagesvs
update_messages,send_draftvsrequest_send,digestvssearch_messages), and the draft tools say where a
revision comes from and what to do afterdraft_conflict.
dove v0.12.0
Added
- Admin web UI, part 1, under
/adminon the existing listener (off by default:[admin_ui] enabled = trueand
origin). Sign-in with a passkey only (dove admin passkey add --name <device>prints a one-time link, valid for
five minutes), sessions of 12 hours (dove admin sessions list|revoke,dove admin passkey list|remove). Pages:
Overview (checks, index status, waiting requests, next sends), Requests (review each draft like a mail and each access
request per mailbox, approve with a fresh passkey confirmation, deny, stop scheduled drafts or end granted access),
Audit (search, verify the chain) and Settings (passkeys, sessions). Every action runs the same admin verb as the CLI
and is audited asweb:<passkey>. The UI only answers on its own host name, refuses anyAuthorizationheader and is
never forwarded by the shipped Caddy and nginx templates;dove tailscale setup --adminmounts it on the tailnet name
and refuses while Funnel is on.
Changed
- Every tool parameter now has a description in
tools/list(origin, format and enforced limits), so agents and
registries no longer have to guess what an id or cursor refers to.
dove v0.11.0
Added
dove mcp catalogdescribes every tool over stdio without a server, mailboxes, keys or network, and refuses every
tool call. The new rootDockerfileruns it, so MCP registries such as Glama can build Dove, list its tools and
score them.- Access requests.
request_accesslets an agent ask for rights on mailboxes of its project (grant syntax, presets
included), for up to 30 days or permanently, with a note for the owner. Nothing changes before the owner approves
withdove requests approve, where mailboxes can be left out (--items) and the time shortened (--duration 2h).
Temporary rights end on their own at the first call after their expiry and never touch the connection's earlier
grants;clients set-grants, revoking the connection, removing the mailbox anddove requests cancelend them
early.list_accountsandclients showshow temporary rights with their end. A mailbox of another project and
an unknown name give the samenot_found.
Fixed
deploy/Containerfilefailed to build from source:.dockerignoreleft outdeploy/release/minisign.pub, which the
binary embeds.
dove v0.10.0
Added
- Scheduled sends approved by the owner.
request_sendasks for own drafts to be sent at fixed times (or spread over
a window), optionally only if nobody answered the thread in the meantime (unless_reply; an answer already in Trash
counts). The owner reviews each
draft withdove requests showand approves withdove requests approve, which binds the exact revision shown;
a draft changed afterwards is not sent. Nothing is retried after an unknown outcome, and an item that could not go
out within[requests] late_grace_minutes(60) is dropped asmissed. Agents follow their requests with
list_requestsandcancel_request. Needsdrafton the mailbox, notsendorallow_send; turn it off with
[requests] enabled = false. Closed requests are deleted after[requests] retention_days(90), and each
connection keeps at most 500 of them.
dove v0.9.0
Added
dove watch create|list|delete|eventsmanage watches from the CLI, anddove watch listenwakes a local agent on
new mail: it pulls watch events over outgoing HTTPS and runs a fixed command once per event, in order, with only
ids and times inDOVE_*environment variables. The cursor is saved after each event (mode 0600), old events are
skipped on the first start, failing commands are retried up to--max-attempts, and a lock file keeps a second
listener on the same profile from running the command twice. Pin it to an agent session with
claude -p --resume <session-id> --fork-session.get_messagereads several messages in one call: passrefsfrom search_messages (oraccount+
message_ids), up to 20. Each id gets its own result or error under batch_read's 64 KiB response budget; a single
id answers as before.create_watchwithkind: no_reply_bytakes amessage_idinstead of athread_id: "tell me if nobody answers
this mail by Friday". The event names that message, so the agent can read it and follow up; a reply from someone
else ends the watch quietly, and a message that is already answered ends it at once (end_reason: replied). CLI:
dove watch create --no-reply-to <message-id> --deadline <time>.
Changed
get_threadis faster. On IMAP all members are read over one connection, with each folder opened once and one
metadata fetch per folder (a one-folder thread of N messages needs N + 2 commands instead of 4N). On Gmail, a thread
asked for bymessage_idneeds one request less. Long threads stop fetching once the body budget is spent. IMAP
reads and index sync no longer send a separate size probe per message.search_messages,digestandsearch_contactsargument errors name the argument and say how to fix it. The
message starts with the path (where.all[1].op: ...), lists what is valid (the operators of that field, the
columns, the fields, what the tool takes) and, where useful, carries a valid example indetails.example. Known
mix-ups get a pointer:containshas no substring match (useterms),senderisfrom,queryand
page_tokenarewhereandcursor,limitover 100 pages withnext_cursor, a time without offset needs
Zor an offset.- These tools accept arguments that have exactly one reading: one mailbox name where a list is expected,
account
foraccounts(andaccountswith one name forsearch_contacts), lists andwherewritten as JSON text,
"true"/"20"fortrue/20, field, operator and enum names in any case, one value where a list is expected
and a one-element list where a string is, a list of words forterms, a singleorder_byobject or
"received_at desc".received_atanddigest'ssincetake a calendar date (2026-11-01, that UTC day) and
sincealso a span back from now (7d,36h). Several values for a one-value argument, a time without offset
and unknown account names are still refused; unknown and out-of-scope mailboxes remain the identicalnot_found. - The audit record of a refused call carries
~invalid=<argument path>inobject_ids(for example
~invalid=where.all[].op): which argument failed, never its value.
Fixed
- Watches no longer treat the owner's own mail from a Gmail send-as address as someone else's: mail with the
SENT
label (or in the IMAP Sent folder) never fires a watch and never counts as a reply, likefrom_selfin search. - A
no_reply_bywatch no longer fires when the reply arrived in the same second as the watched message (IMAP dates
have no milliseconds) and the sync hook had not seen it, for example because it was indexed during a rebuild.
dove v0.8.1
Changed
- Installing and updating the client no longer needs root. The README and the deployment guide now separate the
server install (| sudo sh, root-owned binary,sudo dove upgrade) from the client install (| sh,~/.local/bin,
dove upgradewithout sudo, or Homebrew).install.shprints thePATHline for your shell (zsh, bash, fish,
others) when its target directory is not inPATH, still without touching shell files, and its closing hint
saysdove upgradewithout sudo after a user install. dove upgradeon a binary in a directory you cannot write now names both ways out:sudo dove upgradeon a server,
and for your own computer how to switch to a user install (remove the root-owned binary, reruninstall.sh
without sudo, keep~/.local/bininPATH).dove upgraderefuses to replace a binary installed by Homebrew (inside aCellar/dove/directory, also when
reached through thebinsymlink) and points tobrew upgrade dove; the daily new-version notice and
dove upgrade --checksay that command for Homebrew installs.
dove v0.8.0
Added
dove sinks add --url-file <path>reads the webhook URL from a file (owner-only mode, trimmed) or from standard
input with-.--urlstill works but warns that the URL lands in shell history and the process list.[clients] redact_secrets_defaultin the service config: whentrue, connections created byclients create,
clients inviteand from invitations (dove login, OAuth approval) hide secrets in overview results unless created
with the new--no-redact-secrets. Off unless set; existing connections keep their setting.
Security
- Setting or removing IMAP junk keywords (
$Junk,$NotJunk,NonJunkand any keyword containingjunkorspam,
in any case) needs thedeletepermission: mail clients train spam filters on them, like moving into or out of
Junk.
Fixed
- The hourly organize budget gives back only failures that certainly changed nothing. A Gmail move out of Trash whose
second write failed, a Gmail write answered with a server error, a lost IMAP STORE or MOVE reply and a copy that kept
its original now count as spent. - IMAP
update_messages: a STORE whose reply was lost, or a failure after part of the change was stored, is reported
asoutcome_unknowninstead ofupstream_errorornot_found.
dove v0.7.0
Agent feedback: answering "whom have I not replied to?" took many calls, broad searches were dominated by the
user's own mail, coverage: partial did not say whether to wait, and snippets could show credentials.
Added
search_messagesthread view:group_by: "thread"returns one row per mailbox and thread with the latest
message's columns,message_count,matched_countand reply status (last_sender_is_self,last_inbound_at,
last_outbound_at,awaiting_reply). The thread filterawaiting_reply: true|falsekeeps threads whose latest
non-draft message is or is not from the mailbox itself. It is a rule over indexed mail, not a judgement whether a
reply is needed; a reply sent from another mailbox is not seen.from_selfas a filter and a column: the mailbox's own address, its IMAP aliases, or theSENTlabel.dove search --from-self,--not-from-self,--threadsand--awaiting-reply.- Optional secret redaction per connection (
dove clients set-redaction <name> on|off,--redact-secretson
clients createandclients invite, off by default). Passwords, keys and one-time codes in subjects and snippets
ofsearch_messages,digestandlist_draftsread[redacted], and rows sayredacted: true.get_message,
get_thread,get_draftand attachments stay complete. The detector is heuristic: secrets without a label or a
known format are not caught.
Changed
- Coverage exceptions and
account_errorsinsearch_messages,digestandsearch_contactscarryresolves
(automatic,operatorornever), ahint, and where knownnext_sync_at,next_retry_atand
retry_after_seconds; partial results carry a top-levelhint. Messages listed but not fetched yet during a
backfill are reported asqueued, not as "pending retry".
dove v0.6.0
Security hardening from an internal audit. Read "Upgrading" before you update a server with webhook sinks.
Changed
- Tools that return mail content (
get_message,get_thread,batch_read,search_messages,get_attachment,
digest,search_contacts,list_drafts,get_draft) mark it as untrusted: structured results carry
untrusted_content_notice, and clients without a currentMCP-Protocol-Versionget the JSON inside the
<untrusted_email_content>envelope. Such clients that parsed the text content as JSON need the current protocol
version orstructuredContent. - The OAuth approval page shows the full redirect URI and, for dynamically registered clients, when and from which
network they registered. A destination outside claude.ai, claude.com, chatgpt.com, chat.openai.com and loopback needs
an extra confirmation. Dynamically registered clients never get an error redirect before approval. /connect/redeemchecks an invitation's--allow-iplist before using it up.- Failed bearer authentication on
/mcpis limited per client address (limits.auth_failures_per_minute, default 60,
IPv6 by /64): further failures get 429, valid tokens are always accepted. Loopback and unknown addresses are
exempt. - Organize tools have an hourly budget per client (
limits.client_organize_per_hour, default 500 messages). Moving
mail out of Spam/Junk needs thedeleteaction. Trash and Junk folders are recognized by name in 17 languages. - Webhook sinks belong to a project (
dove sinks add --project,dove sinks set-project); clients only see sinks of
their own project. Sink URLs are sealed by the vault, re-sealed bydove keys rotate, anddove sinks listshows
only their origin. dove upgrade --versionrefuses an older release unless--allow-downgradeis given.install.shandinstall.ps1require the exact trusted commentdove <tag>; the wget fallback is HTTPS only.- Only Dove and its HTTP/TLS/SQLite stack may write log lines. The mail and attachment parsers (lopdf, html5ever and
others) quoted untrusted content, such as PDF font dictionaries, in their warnings. - Deployment: the nginx templates take the client address from the TCP peer even when another site sets
real_ip_headerin thehttpcontext, and log Dove requests without query strings (OAuth codes). The systemd and
.debunits forbid executing files from Dove's writable directories (NoExecPaths=). The release workflow refuses
a tag whose commit is not onmain.
Upgrading
- The state store migrates once on start. Existing webhook sinks keep their watches and queued pushes but serve no
client until you assign a project:dove sinks set-project <name> --project <project>. - An agent that moved mail out of Spam with only
organizenow needsdeletefor that mailbox.
dove v0.5.0
Added
- Local mode (
mode = "local"): Dove on your own computer as your user, for MCP hosts on the same machine, over
http://127.0.0.1:<port>(plain HTTP only for that loopback origin; server mode is unchanged).dove local init
creates a per-user config, keys and data directory,dove local startruns it in the foreground,dove local stop
stops it, anddove local install/uninstall/statusmanage a login item (systemd user unit, launchd agent or
Task Scheduler logon task, no administrator rights). See the threat model in the deployment guide's Local mode section.
Thedoveclient sends credentials to a plain-http loopback server only when your OS user holds that port
(Linux, Windows). - Windows support in the code: keys protected with DPAPI, private directories by access list, the admin channel as a
per-user named pipe with a Windows account check, anddove upgradereplacing the runningdove.exe. dove mcp install --client claude-desktopadds Dove to Claude Desktop'sclaude_desktop_config.json.- Windows release:
dove-<tag>-x86_64-pc-windows-msvc.zip(signed like every other asset), installed with
install.ps1. Every Windows release is smoke-tested on a Windows runner (install, local mode start and stop, upgrade).
Fixed
- Index reads enforce their deadline with a SQLite progress handler. The previous timer interrupt was lost when the
deadline passed before the first statement started (a slow machine or a slow connection open), and the query then
ran without a time limit.