Cabalmail

Host your own email and enhance your privacy

View the Project on GitHub cabalmail/cabal-infra

BIMI end-to-end

Context

BIMI (“Brand Indicators for Message Identification”) lets receiving mail clients display a sender’s brand logo alongside each message in the mailbox list. A sender publishes a TXT record at default._bimi.<domain> pointing at an SVG Tiny PS logo; the receiving client looks the record up at render time, fetches the SVG, and shows it in the message row. It is increasingly the default visual treatment in Gmail, Apple Mail, Yahoo, Fastmail, and others.

This plan was originally written to fix four problems at once. Two had already landed piecemeal during 0.9.x/0.10.x (see Already shipped); the remaining three phases (A/B/C below) are now implemented on the feature branch and pending deploy + on-device/inspector verification.

Erratum (2026-08-07): Since this was written, phases A, B, and C all merged to main and deployed; only on-device/inspector verification of the rendered logos remained outstanding. The as-shipped reference documentation is now docs/bimi.md. The three problems they address:

  1. fetch_bimi is not spec-correct. The Lambda at lambda/api/fetch_bimi/function.py has had its DNS timeouts and exception handling hardened, but it still walks superdomains instead of computing the organizational domain, parses the TXT record positionally, returns the third-party logo URL verbatim (no fetch, no validation, no caching on infrastructure we control), and still falls back to a guessed favicon.ico. See “Remaining fetch_bimi defects” below.
  2. React admin app has no BIMI display. The API client method getBimiUrl is exposed (around line 120) but nothing in react/admin/src/Email/Messages/ calls it. The Envelope.jsx row still leads with an unread dot and a selection checkbox, no logo. The Apple clients already ship the equivalent display (see below); React needs to reach parity.
  3. Cabalmail’s own mail domains do not publish BIMI. There are no default._bimi.* TXT records in terraform/infra/modules/app/, so mail we originate appears in other clients without our mark even when those clients support BIMI.

Already shipped

Goals

Non-goals

Remaining fetch_bimi defects

lambda/api/fetch_bimi/function.py, after the DNS/exception hardening, still has the spec-correctness defects below. The favicon fallback path is the whole tail of the file.

Publishing infrastructure (reference)

Phases

Each phase ships independently. Phase B (React) routes through stage per CLAUDE.md; Phases A and C qualify as direct-to-prod-eligible (no data plane impact, no user-facing surface change in isolation, no IAM implications, purely additive).

Erratum (2026-08-15): The direct-to-prod scaffolding carve-out was retired from CLAUDE.md; Phases A and C also route through stage -> main.

Phase A: spec-correct fetch_bimi

Goal: turn the Lambda into a defensive proxy that returns either a validated, rasterized PNG URL on infrastructure we control, or null. (DNS timeout bounding and broad exception handling are already in place; do not re-do them.)

Scope discovery (From domain, then org domain).

Parsing.

Fetch, validate, rasterize.

Cache and return.

Unit tests in lambda/api/fetch_bimi/ cover (DNS, fetch, and S3 stubbed so the suite stays hermetic; the rasterizer runs on a tiny fixture SVG):

Phase B: React client display

Goal: parity with the shipped Apple AvatarView - a BIMI logo, falling back to a natively drawn initials avatar, in the Envelope.jsx leading slot. Mirror the Apple precedence and fallback rather than reintroducing glyph SVGs; the unread dot stays.

Phase C: publish BIMI for Cabalmail domains

Goal: mail Cabalmail sends publishes a working default._bimi record pointing at the Cabalmail mark, so receivers that support BIMI show our logo.

Verification

Each phase verifies end-to-end before merging.

Future work

Rollout notes