Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rules

Rules live in rules.toml next to config.toml. Postbode reads the file on every sync. A file that fails to validate is rejected as a whole, and the previous rules stay active. postbode rules check validates the file; postbode rules test shows what each rule would do to the mail Postbode has cached.

[[rules]]
name = "github to folder"
match.header = { name = "List-Id", contains = "github.com" }
actions = [{ move = "Lists/GitHub" }, "mark_read"]

Rule keys

KeyDefaultMeaning
namerequiredUnique. Renaming a rule makes it a new rule.
accountevery accountOnly for this account.
folderINBOXThe folder the rule watches.
enabledtruefalse skips the rule. Proposals start disabled.
proposed_bynoneSet by postbode rules propose.
matchrequiredConditions that must all hold. At least one.
actionsrequiredWhat to do. At least one.

Conditions

Text conditions take exactly one of:

  • contains: a case-insensitive substring.
  • equals: the whole value, case-insensitive. On from, to and cc it also matches any single address in the field, so equals = "a@example.com" matches Alice <a@example.com>, b@example.com.
  • regex: Rust regex syntax. Start with (?i) for case-insensitive.
KeyTakesMatches
from, to, cc, subjecttext conditionThat header.
bodytext conditionThe plain-text body; HTML mail is converted. Postbode downloads the body of new mail in the rule’s folder for this.
headertext condition plus nameAny header, such as List-Id.
older_thanduration: 30m, 1h, 2daysMail that arrived at least this long ago.
seentrue or falseRead or unread mail.
to_metrue or falseTo, Cc or Delivered-To holds your address or an alias. false catches list and bcc mail.
aliasaddress, * as wildcardMail sent to that alias.

Actions

ActionEffect
"delete"Saves the message as .eml in the local trash, then removes it from the server. Later rules don’t run for that message.
"mark_read"Marks it read.
"flag"Flags it.
"archive"Moves it to the server’s Archive folder.
{ move = "Folder/Sub" }Moves it to that folder, creating the folder if needed.
"notify"Notifies even when the message was moved.
"silent"Never notifies.

Flags are set before a move. Only the first move or archive that matches a message runs.

When rules act

  • On every sync, in file order. Because rules run again on each sync, older_than and seen can fire later, for example an hour after you read a sign-in code.
  • A rule acts only on mail that arrived after the rule was enabled, so adding a rule never touches your history. postbode rules apply-existing NAME is the explicit opt-in. Run it with --dry-run first.
  • A rule approved with postbode rules approve acts on mail that arrives after the approval. A rule you add or enable by editing the file acts on mail that arrives after the next sync picks it up.
  • Renaming a rule, or disabling and enabling it again, restarts that clock.
  • Mail restored with postbode trash restore carries the $PostbodeRestored keyword. Rules never act on it again. This needs a server that accepts custom keywords; without one, the same rule can delete restored mail again.

Notifications

New INBOX mail notifies unless a rule moved or deleted it, or a matching rule says silent. notify forces a notification for moved mail. With notify = false on the account, only rules that say notify notify. Deleted mail, and mail found by the first sync of a folder, never notifies.

Examples

Delete sign-in codes and magic links an hour after you read them:

[[rules]]
name = "purge sign-in codes"
match.from = { regex = "no-?reply@" }
match.subject = { regex = "(?i)sign.?in|verification code|magic link" }
match.older_than = "1h"
match.seen = true
actions = ["delete"]

Move list mail that is not addressed to you, without a notification:

[[rules]]
name = "list mail"
match.to_me = false
match.header = { name = "List-Unsubscribe", regex = "." }
actions = [{ move = "Lists" }, "silent"]

Give a shop alias its own folder, but still notify:

[[rules]]
name = "shop alias"
match.alias = "*@shop.example.com"
actions = [{ move = "Shopping" }, "notify"]

Archive read mail after 30 days:

[[rules]]
name = "archive old read mail"
match.seen = true
match.older_than = "30days"
actions = ["archive"]

Proposals

Agents never edit rules.toml. They run postbode rules propose, which appends the rule with enabled = false and proposed_by set. To review a proposal:

  • postbode rules list shows every rule with its state and proposer; a pending proposal is off with a proposer.
  • postbode rules test NAME previews what a proposal would do.
  • postbode rules approve NAME enables it.
  • postbode rules reject NAME removes it.

postbode rules schema prints the JSON Schema of this file, and rules.schema.json in these docs holds the same schema.