fable
Create a Fablemail account, sign in, search your mail, and send messages without leaving your terminal.
Quick start
Section titled “Quick start”curl -fsSL http://app.fable.email/cli/install | sh -s -- signup
This installs the fable command and walks you through sign-up. Once it finishes, sign in on the web. In a new terminal, fable signup does it again.
Install
Section titled “Install”fable is a single file with no dependencies. It needs Node.js 18 or newer and curl. The installer puts it at ~/.local/bin/fable and, if that directory is not already on your PATH, adds it to your shell startup file. Open a new terminal afterwards.
curl -fsSL http://app.fable.email/cli/install | sh
Running it again updates fable. You can also download the script from your browser.
Sign up interactively
Section titled “Sign up interactively”fable signupThe CLI asks for:
- Your first and last name.
- The address you want (the part before
@fable.email). It checks availability right away and asks again if the address is taken. - A password, entered twice. Nothing is shown while you type.
It then runs a quick proof-of-work check (the same one the sign-up page runs in your browser), creates the account and prints your new address.
Create a Fablemail account on http://app.fable.email
First name: Ada
Last name: Lovelace
Email address (the part before the @): ada
[email protected] is available.
Password (at least 10 characters):
Confirm password:
Running a quick verification... done.
Creating your account...
Your account is ready: [email protected]
Sign in at http://app.fable.email/login
Sign up from a script
Section titled “Sign up from a script”Pass every field as a flag to skip the prompts. The password is read from standard input so it doesn't end up in your shell history or the process list:
printf '%s' "$PASSWORD" | fable signup \
--first-name Ada --last-name Lovelace --address ada --password-stdin
Without a terminal to prompt on, the CLI fails if a field is missing. It exits with status 0 on success and 1 on any error, with the reason on standard error.
Sign in
Section titled “Sign in”fable loginThis opens your browser on this server. Sign in if you need to, then allow the terminal to use your mailbox for 48 hours. The browser sends the session token only to 127.0.0.1 on your computer. The CLI stores it in ~/.config/fable/credentials.json (or $XDG_CONFIG_HOME/fable/credentials.json) and no one else can read the file.
fable whoami prints the address and when the session expires. fable logout deletes it here and on the server. Changing your password ends it too.
List, search, and read
Section titled “List, search, and read”list shows your inbox, newest first. search matches words (the same keyword index as the website). It does not search by meaning. Spam is left out of that index, so a word search only includes spam when you pass --folder spam.
fable list --unread --limit 10fable search lease renewal --from adafable read <message-id>--folder is inbox, sent, spam, or all. --from, --to, and --subject keep messages whose sender, recipient, or subject contains that text. --after and --before take a date or time. Add --json for the API response.
Repeat --to, --cc, or --bcc, or separate addresses with commas. The body is --body, --body-file, or standard input when it is not a terminal. Sending follows the same undo window as the website.
Commands and options
Section titled “Commands and options”- Addresses are 3-30 characters: letters, numbers, dots, underscores and hyphens. Some are reserved.
- Passwords need at least 10 characters, can't be a common password and can't contain your name or address.
- Each network can create up to 5 accounts per hour. Attempts rejected for invalid fields don't count.
- Add a recovery email in Settings after signing in. Without one, a forgotten password can't be reset.
HTTP API
Section titled “HTTP API”Sign-up is public. Everything else under /api/v1 needs the session token from fable login:
Authorization: Bearer <token>The token lasts 48 hours. These routes ignore the browser cookie.
GET /api/v1/me
Section titled “GET /api/v1/me”Returns { address, firstName, lastName, expiresAt }.
POST /api/v1/logout
Section titled “POST /api/v1/logout”Revokes the bearer token.
GET /api/v1/messages
Section titled “GET /api/v1/messages”Lists messages, newest first. Query parameters: folder (inbox, sent, spam), from, to, subject, unread=1, after, before, limit (1–100, default 25), offset (0–1000). Returns { messages }. Each message has id, threadId, folder, subject, fromName, fromAddr, toAddrs, ccAddrs, snippet, date, isRead, and hasAttachments.
GET /api/v1/messages/<id>
Section titled “GET /api/v1/messages/<id>”One message, plus text and attachments (id, filename, contentType, size). Unknown ids return 404.
GET /api/v1/search?q=lease+renewal
Section titled “GET /api/v1/search?q=lease+renewal”Keyword search only. q is required and uses the same word matching as the website (stems and quoted phrases). It does not search by meaning. The same filters as the list apply. Spam is included only with folder=spam, and then the match is a plain text search. Returns { query, messages }.
POST /api/v1/send
Section titled “POST /api/v1/send”Same JSON body as the website's send endpoint:
curl -X POST http://app.fable.email/api/v1/send \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"to":["[email protected]"],"subject":"Hello","text":"Hi"}'
Returns 201 with { ok, id, threadId, queueId, undoMsLeft }. 400 when the body is invalid, 401 when the token is missing or expired, 429 when you are sending too fast or over the daily recipient limit.
Sign-up uses three public endpoints.
GET /api/captcha
Section titled “GET /api/captcha”Returns a proof-of-work challenge: { algorithm, challenge, salt, maxnumber, signature }. Find the integer number from 0 to maxnumber where sha256(salt + number) as hex equals challenge. The token is the base64-encoded JSON { algorithm, challenge, number, salt, signature }. Each token works once and expires after 10 minutes.
GET /api/signup/availability?local=ada
Section titled “GET /api/signup/availability?local=ada”Returns { available, error, address }. This is only a hint; sign-up itself is the final check.
POST /api/v1/signup
Section titled “POST /api/v1/signup”curl -X POST http://app.fable.email/api/v1/signup \
-H 'Content-Type: application/json' \
-d '{"firstName":"Ada","lastName":"Lovelace","local":"ada","password":"…","captcha":"<token>"}'
Returns 201 with { ok: true, address, loginUrl }. Errors return { error } plus:
400withfieldErrors(keyed byfirstName,lastName,local,password) when a field is invalid, orcaptchaFailed: truewhen the token is wrong, used or expired.409when the address is taken.429when your network hit the sign-up limit.