Capture email¶
Forward an article, a newsletter or a PDF to yourself and get a note. It is optional and built for Gmail. Everything about mail is read-only: the worker only peeks at messages and cannot delete them.
What you need¶
- A Gmail account with 2-Step Verification on (Google only offers app passwords then).
- A place to keep the app password: the macOS Keychain on a Mac, or, on a system that is not supported but may work, a Secret Service (GNOME Keyring or KWallet running in your login session). A server, a container, WSL and cron have none, so email capture does not work there: see Email where there is no keyring.
- A model on your machine for email, because email is never shown to a model that sends text away: see Keep email local.
Set it up with the wizard¶
sb setup email
It walks you through six stages, telling you exactly what to click, and writes the settings for you:
- Check that 2-Step Verification is on.
- Create a Gmail label named
esbi-cli. - Create a Gmail filter: mail to
you+esbi-cli@gmail.comgets the label and skips the inbox, so these mails never mix with your own. - Create an app password called
esbi-cli, and copy it. - Save the settings, then paste the password: it is typed hidden and goes straight into the Keychain, never through the wizard, never into a file.
- Test it: forward a mail to that address, and the wizard fetches it.
You can stop with Ctrl-C and run it again; nothing changes until stage 5. If you prefer to do it by hand:
sb email configure --user you@gmail.com
Mail capture on for you@gmail.com, label esbi-cli, in ~/.config/esbi-cli/config.toml
sb email set-password
App password for you@gmail.com:
Saved to the Keychain.
The first writes the [email] block of the config; the second prompts for the app password (hidden). pbpaste | sb email set-password --stdin pastes it without typing. Spaces in the password Google shows are dropped.
Use it¶
Forward anything to you+esbi-cli@gmail.com. The next sb run (or the nightly job) turns it into a note. To fetch right away:
sb email fetch
Mail: 2 saved, 0 duplicates, 0 failed, 1 images saved, 3 links queued.
(images saved, links queued and not marked as read appear only when they are not zero.) What happens:
- The worker reads the label's mail, whether you opened it or not, and skips any it already saved (by Message-ID). It marks a mail as seen only after its note is written. See Which mail is taken.
- If the server refuses to mark a mail as read, the line says how many:
Mail: 2 saved, 0 duplicates, 0 failed, 1 not marked as read (the server refused).The mail is still saved, and it is not saved twice the next time. - Each mail becomes a Markdown file in
inbox/, then a note like any other source, withkind: email. - A PDF attached to a mail is saved in
inbox/too, and becomes its own note. - An image attached to a mail (PNG, JPEG, WEBP or TIFF) is saved in
inbox/next to the mail's note, and follows the same road as any image you drop there: read by the local[llm.ocr]model when reading images is on, and listed bysb scanas not read (with the hint to turn it on) when it is not. The bytes decide what a file is, not the name or the declared type. Signatures, logos and tracking pixels are skipped (under 200 pixels or a few KB), and so are images over 5 MB or beyond 15 MB per mail. - Blank "padding" characters that newsletters add to their preview text are removed.
- Links inside a mail are not followed unless you turn on
follow_links: the mail body is the source. - A mail that cannot be read is reported once (
Mail: ... 1 failed) and then skipped; its id is kept in.esbi/mail-failed.txt. - Nothing is ever deleted from your mailbox.
Which mail is taken¶
The mailbox search takes three kinds of mail from the label, and the worker skips any it already saved:
- Every unread mail in the label, however old.
- Every mail added to the label after the highest UID taken so far, even if you already opened it. The UID is the number the mail server gives each message in a mailbox; it is remembered in
.esbi/mail-state.jsonin the vault, for the current generation of the mailbox (the server's UIDVALIDITY: if Gmail ever renumbers the mailbox, the memory is not used and starts again). - Every mail from the last 14 days, whether you opened it or not.
The 14 days count the day a mail was received, not the day you put the label on it. That is why the first two rules exist: a mail you labelled yesterday that arrived last month is outside the window, and without them it would never be seen.
Email where there is no keyring¶
Email capture needs a password store, and a headless Linux has none (macOS is the only supported system, and it has the Keychain). The keyring library talks to a Secret Service, which means GNOME Keyring or KWallet running in your login session. A Linux desktop has one; a server, a container and WSL usually do not, and cron cannot reach a desktop keyring either, so the nightly job could not read the password even where you stored it. Without one, sb email set-password and sb email fetch say that there is no keyring on the system and where email capture works. Installing keyrings.alt is not advised: it keeps the password in a plain file.
Only macOS is supported, so this is a documented limit and not something to work around: on those systems, leave email capture off. The rest of esbi-cli does not need a keyring.
Follow the links in a mail¶
Off by default. A newsletter often says little and links to the article: with
[email]
follow_links = true
follow_links_max = 3
the http(s) links in the text of each newly saved mail (at most follow_links_max, from 1 to 10, per mail) are queued, never fetched while the mail is captured. The nightly run reads them like any queued source, with its limits, and treats the result as email: only your local [llm.private] model writes the note, the note has kind: email, and a cloud model is never shown it. Tracking redirects, unsubscribe, preferences, view-in-browser and account-action links (sign in, confirm, reset password...) and pictures are skipped, and a link that is already queued or already a note is not queued again. Only links that show as text in the mail are found; one hidden behind a button in an HTML-only mail is not.
Think before turning it on: opening a link is a visit. The site, or whoever put the link in the mail, sees a request from your machine. Every fetch still goes through the check that only public addresses are reached (and environment proxies are ignored unless you set use_environment_proxy). sb email configure keeps these two settings when it rewrites the [email] block. See Links in a mail.
Keep email local¶
Email is private by design. A model that sends text out (an API, your Claude subscription or ChatGPT plan, an Ollama or LM Studio server on another machine) is never shown email. With a local model you need do nothing. If any of your models is a cloud kind, add a local one for email:
[llm.private]
model = "ollama/llama3.2:latest"
sb init --model subscription or --model api writes it for you. With it, three things hold:
- An email is written only by
[llm.private]. Without it, an email is refused withan email cannot be written by a model that sends text away: add a local model as [llm.private] in config.toml. - When a cloud model writes any other source, pages made from email, and concept pages built only from email, are left out of what it sees.
sb askwith a cloud model leaves out email pages and the email sections of shared pages, and the consolidated summary of a page that has one.
Images from mail and the pages that a mail's links lead to count as email too: same private model, same rules.
sb doctor shows where you stand:
ok email privacy: email is read only by a model on this machine
or WARN email privacy: ... send text away and no [llm.private] is set: emails will be refused, or FAIL email privacy: [llm.private] (or its fallback) sends text away. Notes made from email are ordinary notes in the vault, so they go wherever the vault goes: the git remote, and the sb export site. Use a private remote and do not publish an export. Details: Safety and privacy.
When something goes wrong¶
| Message | Cause and fix |
|---|---|
error: email is disabled: set [email].enabled = true in config.toml |
Mail capture is off: run sb email configure --user ... |
No IMAP password in the Keychain for you@gmail.com. Store it with `sb email set-password`. (system keyring instead of Keychain off macOS) |
Run sb email set-password |
There is no keyring on this system to keep the password in ... |
Linux without a Secret Service (a server, a container, WSL): see Email where there is no keyring |
The system keyring did not answer: ... |
Off macOS, the keyring is locked or waiting for you: unlock it, or store the password again |
1 not marked as read (the server refused) in the Mail: line |
The mail was saved anyway. Mark it as read in Gmail by hand if it keeps coming back as unread |
IMAP login/select failed for you@gmail.com@imap.gmail.com: ... |
Wrong app password (not your normal password), or the account lacks 2-Step Verification. Create a new app password and store it again |
Mailbox 'esbi-cli' not found on imap.gmail.com |
The Gmail label is missing or named differently; fix it, or [email].mailbox |
The Keychain is waiting for permission: a dialog on the Mac asks... |
Click Always Allow on the macOS dialog, or store the password again |
Could not write to the Keychain: ... with The old item belongs to another program |
Run the security delete-generic-password -s esbi-cli-imap -a you@gmail.com it prints, then sb email set-password |
warning: mail skipped: ... during sb run |
Mail never stops a run: the rest was processed. Run sb email fetch to see the same error alone |
More in Troubleshooting.