Skip to content
Reference

Configuration reference

Settings live in ~/.config/inboxminder/config.toml. Setup writes it, Preferences and the sources editor update it through a validated funnel, and it is safe to edit by hand. Every key is optional; a missing key takes the default shown here. Secrets never go in this file. They live in the macOS Keychain (see Secrets).

The schema in src/config/schema.ts is the authority; a value it rejects fails loudly when the agent starts, with the reason in the log.

A settings change applies to the agent after a restart: Reinstall agent in the menu bar, or inboxminder agent install.

[llm]

The model that classifies mail and writes drafts.

[llm]
provider = "anthropic"      # anthropic | openai | google | openai-compatible
model = "claude-sonnet-5-5"
# baseUrl = "http://localhost:11434/v1"   # openai-compatible only

openai-compatible covers Groq, OpenRouter, Ollama, LM Studio, vLLM and anything else that speaks the OpenAI API. baseUrl defaults to a local Ollama. Local servers need no key.

[embedding]

The model that turns your docs and sent mail into vectors for the index.

[embedding]
provider = "openai"         # openai | google | openai-compatible (not anthropic)
model = "text-embedding-3-small"
dimensions = 1536
# baseUrl = "http://localhost:11434/v1"

Changing any of these invalidates every stored vector. Edit the file, then run inboxminder index --full; content hashes are keyed on the embedding config, so everything re-embeds. A fully local setup pairs an OpenAI-compatible embedding model (for example nomic-embed-text on Ollama, dimensions = 768) with a local chat model.

[engine]

[engine]
enabled = true              # false = triage and labels only, no drafts

[email]

[email]
pollIntervalSec = 45        # minimum 10
skipSenders = ["mailer-daemon"]   # address substrings ignored with no classifier call
notifications = true        # macOS notifications; subjects only
updateCheck = true          # daily notify-only version check against the release feed
maxContextChunks = 8        # knowledge passages per draft (1 to 100)
maxStyleExamples = 3        # past replies used as style reference (1 to 100)
maxThreadMessages = 4       # thread history in the prompt (1 to 100)
classifyBeforeDrafting = true
draftSelfNotifications = false   # draft replies to mail your own address sends itself (contact forms)
indexRefreshHours = 24      # periodic source re-index; 0 = startup only (max 720)
sentBackfillLimit = 200     # sent messages per index run; older history backfills batch by batch (max 2000)
automatedSent = "skip"      # skip | flag | off: automated sent mail as a tone example

skipSenders matches on the address, never the display name.

[connectors]

What the index is built from. See Knowledge sources.

[connectors.files]
enabled = true
dirs = ["/Users/you/notes"]

[connectors.llmstxt]
enabled = false
urls = ["https://docs.example.com/llms-full.txt"]

[connectors.github]         # cloned with your own git credentials; private repos work
enabled = false
repos = [{ url = "https://github.com/you/your-docs", branch = "main" }]   # branch optional

[connectors.notion]         # token: inboxminder set-key notion
enabled = false

[connectors.linear]         # key: inboxminder set-key linear
enabled = false

[connectors.slack]          # user token with channels:history: inboxminder set-key slack
enabled = false
channels = ["C0123456789"]

Notes written in the window and canonical answers are indexed too, with no connector section of their own.

[triage]

The gatekeeper. See Triage and labels.

[triage]
enabled = true
archive = []                # categories that also skip the inbox, e.g. ["newsletter", "marketing"]
coldOutreachHint = ""       # free text added to the cold-outreach criteria (max 500)

[triage.labels]
newsletter = "Newsletter"
notification = "Notification"
marketing = "Marketing"
"cold-outreach" = "Cold Outreach"   # the hyphen needs quotes in TOML
fyi = "FYI"
important = "Urgent"        # urgent reply-worthy mail; "Important" is a Gmail system label and is rejected

Categories are fixed: newsletter, notification, marketing, cold-outreach, fyi. A "/" in a label name nests it in Gmail's sidebar. Any label name that collides with a Gmail system label (inbox, spam, trash, unread, starred, important, sent, draft, drafts, chat) is rejected at config load.

[labels]

Thread-state labels.

[labels]
enabled = true
autoResolve = false         # flip Pending to Resolved when your own reply is observed
pending = "Pending"
resolved = "Resolved"

[drafts]

[drafts]
signature = ""              # appended after generation; the model never sees it (max 1000)
style = ""                  # free-text tone, joins the standing instructions (max 2000)
coverage = "all"            # all | confident-only (skip only an explicit "unsure" verdict)
polish = "tone"             # tone | light: how far Polish in my tone may go
deleteOnReply = false       # your own reply deletes the app's unsent draft on that thread
maxAgeDays = 0              # sweep the app's unsent drafts older than N days; 0 = never (max 90)

Only drafts the app itself created, and that Gmail confirms are still unsent, are ever deleted.

[instructions]

Per-sender steering. See Drafting.

[[instructions.rules]]
match = "@acme.com"         # substring of the sender ADDRESS (max 200)
note = "Enterprise customer; always offer a call."   # max 2000

[[instructions.rules]]
match = "billing@"
note = "Never quote prices; leave a [TODO]."

Up to 100 rules. Global standing instructions live in ~/.config/inboxminder/instructions.md, not in TOML.

[followup]

See Follow-ups.

[followup]
enabled = false
mode = "label"              # label | time | both
intervalDays = 4            # time and both modes
maxFollowups = 1            # nudges per thread; 0 disables drafting
scanMinutes = 15            # how often the label scan runs
markUnread = false          # mark a resurfaced thread unread (noisy on a shared inbox)
readyLabel = "Ready for follow-up"   # applied to every drafted nudge until it is sent, answered or swept

[followup.labels]           # label you add in Gmail -> quiet days; at most 8
"Follow up/3d" = 3
"Follow up/1w" = 7
"Follow up/2w" = 14

[tickets]

See Linear.

[tickets]
enabled = false
teamId = ""                 # the Linear team; Preferences fills it in
resolutionDrafts = true     # "it's fixed" draft when a linked ticket completes
checkMinutes = 30           # how often linked tickets are polled

[tickets.assignLabels]      # Gmail label -> Linear account email; at most 16
"Assign/Priya" = "priya@example.com"

[tickets.assigneeSuggestions]   # topic label -> name written into the ticket description as a suggestion
"Billing" = "Dan"

An empty assignLabels means no hand-offs. An owner is never inferred.

[answers]

See Drafting.

[answers]
enabled = false
dir = ""                    # a local folder of Q&A markdown snippets, e.g. "~/inboxminder-answers"
maxPerDraft = 2             # 1 to 5
escalate = true             # write <slug>.suggested.md for unanswered questions

[feedback]

[feedback]
enabled = false             # learn from the edits you make before sending
maxCorrections = 2          # correction pairs per draft prompt (1 to 10)
maxStored = 300             # corpus cap; oldest pruned (max 2000)

[docs]

See Knowledge sources.

[docs]
enabled = false
repoPath = ""               # a local docs repository working tree; ~ allowed
sources = ["llmstxt"]       # which indexed sources count as "your docs" for the gap check
instructions = ""           # voice, structure, components, topics never to document (max 4000)

[mcp]

Config-file only. See Live lookups.

[mcp.servers.billing]
transport = "stdio"
command = "npx"
args = ["-y", "@acme/billing-mcp"]
envSecrets = { BILLING_API_KEY = "mcp-billing" }   # env var name -> Keychain entry name
readOnlyAnnotationsOnly = true
allowTools = ["get_customer", "list_invoices"]

[mcp.servers.crm]
transport = "http"
url = "https://mcp.example.com"
headerSecrets = { Authorization = "mcp-crm" }      # header name -> Keychain entry name

Store each referenced secret with inboxminder set-key <entry name>.

Secrets

Everything secret is in the macOS Keychain under the service inboxminder (a profile uses inboxminder.<profile>). Store or rotate one with inboxminder set-key <name>, then restart the agent.

EntryWhat
anthropic, openai, google, openai-compatibleAI provider keys, by provider name. openai also serves OpenAI embeddings
gmail-client-id, gmail-client-secretYour Google Cloud OAuth client
gmail-tokensGmail access and refresh tokens, written by auth
licenseYour license key, written by license activate
notion, linear, slackConnector tokens
any name referenced by [mcp]MCP server secrets

In a profile, the Google client and provider keys fall back to the base service when the profile has none of its own; Gmail tokens, the license and connector or MCP tokens never do. See Profiles.

Files

PathWhat
~/.config/inboxminder/config.tomlSettings (this file)
~/.config/inboxminder/instructions.mdStanding instructions for every draft
~/.config/inboxminder/notes/Notes written in the window
~/.inboxminder/state.dbSQLite: threads, drafts, queue, dedup, activity
~/.inboxminder/index/LanceDB vector index
~/.inboxminder/status.jsonThe versioned status file the app renders
~/.inboxminder/logs/watch.logThe log (pino JSON); watch.err.log holds crashes
~/Library/LaunchAgents/com.inboxminder.agent.plistThe launchd agent

INBOXMINDER_CONFIG_DIR and INBOXMINDER_DATA_DIR relocate the two directories for testing. A profile nests under profiles/<name> in each.

Last updated 14 October 2026. Questions: hi@kelviq.com.