bifrost
bifrost
From
bifrost 1.25.1, a command-line mail client
To
stdout, and whatever is reading it
Content-Type
application/json, or text/plain for humans
X-Protocols
IMAP, SMTP, MIME
X-License
MIT, written in Go

Subject

A mail client with no one at the keyboard.

Bifrost speaks IMAP, SMTP and MIME against any standards-compliant provider. The CLI is built for non-interactive and agent-driven use: no TTY assumptions, no editor spawning, no prompts, and structured --json output on every command.

go install github.com/lgforsberg/bifrost/cmd/bifrost@latest

The bridge

One command, two readers.

Every command renders for a person by default and for a program with --json. Same call, same data, two readings. Cross the bridge.

$ bifrost inbox --limit 3
UID   FLAGS  DATE              FROM                SUBJECT
----  -----  ----------------  ------------------  ----------------------------
1841  R      2026-08-11 09:14  Skatteetaten        Your tax return is available
1842         2026-08-11 11:02  Nordic Domain Days  Re: Speaker confirmation
1843  RF     2026-08-11 16:47  DNB                 Mortgage: documents received

Columns are padded to fit. R read, F flagged, A answered, D draft.

$ bifrost --json inbox --limit 3 | jq -c '.[] | {uid, subject, flags}'
{"uid":1841,"subject":"Your tax return is available","flags":["\\Seen"]}
{"uid":1842,"subject":"Re: Speaker confirmation","flags":[]}
{"uid":1843,"subject":"Mortgage: documents received","flags":["\\Seen","\\Flagged"]}

Raw output is indented JSON with camelCase fields and full addresses. jq is only here to keep the example short.

The contract

Failure is data too.

A caller that cannot read prose needs to know what went wrong without parsing a sentence. Errors come back in the same shape as everything else, with a code that is safe to branch on.

$ bifrost --json read 999
{"error": true, "code": "NOT_FOUND", "message": "message uid 999 in INBOX: not found"}
  • NOT_FOUNDMessage UID or resource doesn't exist
  • ALREADY_EXISTSFolder already exists
  • AUTH_FAILEDIMAP/SMTP authentication failed
  • CONNECTION_FAILEDCould not connect to server
  • SEND_REJECTEDSMTP server rejected the message
  • PENDING_APPROVALDraft is awaiting approval
  • CONFIG_ERRORConfiguration is missing or invalid
  • USAGE_ERRORBad flags or arguments
  • INTERRUPTEDCancelled before completing
  • UNKNOWNAnything not otherwise classified

What it does

A whole mail client, minus the interface.

IMAP

Folder management, envelope listing with pagination, message fetch, move, delete and flag, and server-side SEARCH. Special folders are found through IMAP special-use rather than guessed by name.

SMTP

RFC 2822 composition, MIME multipart with attachments, and plus-address (user+tag@) envelope handling.

Threading

Conversations reconstructed across folders through In-Reply-To and References, with reply and forward headers that Gmail and Apple Mail agree with.

Drafts

Save, list, send and delete, with an optional approval keyword so a draft written by a program waits for a human before it goes out.

Multi-account

Flexible account matching by partial name, and per-account overrides for Sent, Drafts, Trash and Archive.

Damaged mail

Parsing is best-effort and says so. Truncated parts and undecodable charsets come back with a warnings list rather than an empty result.

The surface

Twenty-odd verbs, grouped by what they touch.

Read

  • inbox list a folder
  • read fetch one message
  • search server-side IMAP search
  • thread follow a conversation

Write

  • send compose and send
  • reply reply in thread
  • forward pass it on
  • draft save, list, approve, send

Organise

  • archive · move · delete
  • mark-read · mark-unread
  • flag · unflag
  • folder list, status, create, rename, delete

Set up

  • config init write a template
  • accounts list what is configured
  • version · help

Global flags go before the command: --account, --json, --verbose, --config, --timeout.

Install

Three commands to a first inbox.

go install github.com/lgforsberg/bifrost/cmd/bifrost@latest

Requires Go 1.25 or newer.

Or build from source

git clone https://github.com/lgforsberg/bifrost
cd bifrost
make build      # produces ./bin/bifrost

Point it at a mailbox

# 1. Generate a config template
bifrost config init

# 2. Edit ~/.bifrost/config.json

# 3. Go
bifrost inbox

Config lives at ~/.bifrost/config.json, overridable with --config or BIFROST_CONFIG. Gmail and Microsoft 365 need OAuth2: set authMechanism and give Bifrost a tokenCommand that prints an access token.

Library

Or skip the CLI and import the engine.

The CLI is a thin shell over mail. The same package is importable on its own.

import "github.com/lgforsberg/bifrost/mail"

client := mail.NewIMAPClient(account, logger)
if err := client.Connect(ctx); err != nil {
    return err
}
defer client.Close()

envelopes, _ := client.ListEnvelopes(ctx, "INBOX", 10, 0)
msg, _ := client.FetchMessage(ctx, "INBOX", envelopes[0].UID, true)

opts := mail.BuildReply(account, msg, "Thanks!", false, true)
res, err := mail.Send(ctx, account, client, opts, true, logger)

Full API reference in mail/README.md.

Bifrost is on GitHub, under MIT.

Issues, forks and patches all welcome.

View the repository Read the manual