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

Markdown Email

AIMX is Markdown-native end-to-end. Inbound mail is stored as Markdown for direct LLM consumption; outbound --body is interpreted as Markdown by default and rendered to HTML on the wire. Agents read Markdown, write Markdown, and the binary handles the MIME plumbing.

How a default send is delivered

  1. Caller submits Markdown. aimx send --body "..." (or the MCP email_send tool with body: "...") ships the Markdown source to the daemon over the local UDS.
  2. Daemon appends the per-mailbox signature to the body so the recipient always sees it in the text/plain region. On the default Markdown path the signature is part of the Markdown source, so it also renders into the HTML alternative (and a [link](url) in the signature becomes a clickable <a>).
  3. Daemon renders Markdown to HTML. The renderer uses comrak configured for CommonMark + GFM extensions (tables, strikethrough, autolinks, task lists, footnotes, tag filter for unsafe tags). Raw HTML embedded in Markdown is escaped, not rendered — operators wanting raw HTML use --html-body instead.
  4. Inlined stylesheet pass. The renderer walks the rendered HTML tree and adds style="..." attributes per element. Inlining is required because Gmail, Outlook for Web, and Yahoo Mail strip or limit <style> blocks.
  5. Multipart assembly. The daemon builds a multipart/alternative MIME message with the Markdown source as the text/plain part and the rendered HTML as the text/html part. With one or more attachments, the multipart/alternative is wrapped in an outer multipart/mixed so attachments sit as siblings of the alternative block.
  6. DKIM signing. The signed body bytes are the canonical message body — the new MIME shape doesn’t affect the signing logic, and DKIM verification works identically across plain-text, Markdown-rendered, and --html-body paths.
  7. MX delivery. The daemon resolves the recipient domain via MX records and delivers the signed message.

The recipient sees:

  • Rich-text clients (Gmail, Outlook, Apple Mail, Thunderbird): the rendered HTML with headings, links, tables, blockquotes, code blocks — all styled by the inlined stylesheet.
  • Text-only clients (mutt, mailx, screen readers in plain mode): the Markdown source verbatim. Markdown is good plain text by design — # Heading is clear, [link](url) shows the URL inline, bullets with - look like bullets.

Supported Markdown features

The default render path supports CommonMark plus GFM extensions:

FeatureMarkdown inputRenders as
Headings# H1#### H4<h1><h4> with sized typography
Paragraphsblank-line separated<p> with comfortable line-height
Bold / italic**bold**, *italic*<strong>, <em>
Strikethrough (GFM)~~strike~~<del>
Links[text](https://url)<a> with non-default link color, no underline at rest
Autolinks (GFM)bare https://example.com<a>
Inline code`code`<code> with monospace + light gray background
Code blocks```lang … ```<pre><code> with monospace + padded background
Blockquotes> quoted<blockquote> with left border
Horizontal rule---<hr> thin neutral rule
Unordered list- item<ul><li> with sensible spacing
Ordered list1. item<ol><li> with sensible spacing
Tables (GFM)| h1 | h2 | / |---|---| / cells<table> with collapsed borders and subtle row separators
Task lists (GFM)- [x] done / - [ ] todocheckbox-prefixed list items
Footnotes (GFM)text[^1][^1]: notenumbered footnote anchor + reference list
Tag filter (GFM)<script>...</script>stripped (security)

Built-in stylesheet

The inlined stylesheet covers the elements above. It is intentionally minimal — operators wanting custom CSS use --html-body for now.

ElementStyle intent
bodysans-serif font stack, line-height: 1.55, max-width: 720px, comfortable padding
h1, h2, h3, h4size scale, modest top margin
p, ul, ol, lisensible spacing
anon-default link color (avoids the browser-default purple-after-visit), no underline at rest
code, premonospace, light gray background, padding
blockquoteleft border, dim foreground
table, th, tdcollapsed borders, subtle row separators
hrthin neutral rule

The stylesheet is self-contained: no <link> to Google Fonts, no remote CSS, no remote images injected by the renderer. Privacy-conscious clients and corporate firewalls do not strip the rendering. The total rendered HTML for a typical 5KB Markdown briefing fits comfortably under 25KB.

If your content uses an HTML element the inlined stylesheet does not cover (e.g. <details>, <sub>), it renders unstyled (browser defaults). The element list above is the v1 scope; expand on real demand.

Body size limit

The renderer enforces a 5 MiB cap on the Markdown source byte length (the constant is MAX_MARKDOWN_BODY_BYTES = 5 * 1024 * 1024). Above the cap, the daemon refuses with the canonical error:

markdown body exceeds 5 MiB; use --html-body for pre-rendered large documents or --attachment for sending the document as a file

Rationale: a 5 MiB Markdown body renders to roughly 15–25 MiB on the wire (Markdown source + HTML + inlined styles, with ~37% base64 overhead). That sits at the edge of mainstream receiver caps — Gmail 25 MB, Outlook.com / iCloud 20 MB, Microsoft 365 up to 150 MB. Send anything larger as an attachment, not as a body.

The cap is enforced at the renderer entry point so all callers (aimx send, MCP email_send, future cron jobs) share one limit. Operators on the wire surface that scripts can branch on the failure see the dedicated ERR BODY_TOO_LARGE ack code; the canonical reason string survives in the wire response for human readers.

Escape hatches

--text-only

Forces the wire to single-part text/plain with --body shipped verbatim. No Markdown rendering, no HTML part, no multipart/alternative wrapper.

aimx send --from alice@example.com --to bob@example.com \
  --subject "Verification code" \
  --body "Your code: 184293" \
  --text-only

The per-mailbox signature is auto-appended on this path. AIMX appends the configured signature (or the built-in default) to the body before encoding, so even one-liner text-only sends carry the configured footer. Disable globally with signature = "" in config.toml if your one-liner shape really needs to ship verbatim.

Use for:

  • OTPs, transactional one-liners, system-generated alerts.
  • Migrating existing scripts that depended on --body shipping text/plain. Adding --text-only preserves the old shape exactly.

--html-body

Supplies a custom HTML body. AIMX uses the --html-body value verbatim as the text/html part and uses --body as the text/plain fallback so text-only clients still see something readable.

aimx send --from alice@example.com --to bob@example.com \
  --subject "Newsletter" \
  --body "Plain-text fallback for text-only clients." \
  --html-body "$(cat newsletter.html)"

The shell pattern --html-body "$(cat template.html)" reads the template from a file and passes it on the command line. Linux’s typical ARG_MAX is around 1MB, well above any reasonable HTML email size. For templates that exceed ARG_MAX, write the template to a tempfile and use a wrapper script — but at that size you should consider attaching the document instead.

--html-body and --text-only are mutually exclusive. clap rejects the invocation before any UDS round-trip. The same canonical error fires server-side on the MCP email_send / email_reply tools so operators see one consistent message regardless of the surface.

The per-mailbox signature is appended to the text/plain fallback so text-only readers still see it. The supplied HTML stays operator-verbatim — if you want the signature visible in the HTML view, include it inside your template. Disable globally with signature = "" in config.toml if you want the text fallback to ship verbatim too.

--html-body bypasses the renderer’s tag-filter and other safety checks; the operator owns the consequences. Do not pass user-controlled HTML through this flag.

Sent storage

Sent records under sent/<mailbox>/ always store the Markdown source (or the literal text for --text-only, or the --body text part for --html-body). The recipient’s HTML view is recoverable by re-running the same renderer on the stored Markdown — output is deterministic given the pinned comrak version.

The frontmatter declares the wire shape so an operator browsing sent/ can tell at a glance what the recipient saw:

outbound_formatWire shapeSent body content
"markdown"multipart/alternative (text + rendered HTML)Markdown source with signature appended before render
"text"single-part text/plainbody with signature appended
"html"multipart/alternative (text + custom HTML)the --body text part with signature appended (custom HTML is not stored)

The outbound_format field appears immediately after outbound = true in the Outbound block of the frontmatter:

outbound = true
outbound_format = "markdown"
delivery_status = "delivered"

Pre-feature sent records lack the field; on read, those default to "text" (the historic single-part text/plain shape) so legacy records keep parsing cleanly.

No .html sibling file is ever written. Operators who need a record of the exact custom HTML they sent should keep their template under version control — the --html-body payload is not persisted by AIMX.

Determinism

The renderer is deterministic: the same Markdown input produces byte-identical HTML output across two invocations. This is what justifies dropping the rendered HTML from sent storage — the recipient’s view is recoverable. comrak is pinned to an exact patch version in Cargo.toml; bumping it requires re-blessing the renderer fixtures and is called out on the corresponding GitHub Release.

CI defends the determinism guarantee with two checked-in fixtures (tests/fixtures/markdown/briefing-5kb.md and tests/fixtures/markdown/report-50kb.md) and their pinned expected outputs. A comrak bump that changes whitespace or attribute ordering surfaces at CI time, not in production.

Worked example

Input Markdown:

# Daily briefing — 2026-05-07

## Open positions

| Symbol | Shares | Cost basis |
|--------|-------:|-----------:|
| AAPL   | 100    | $150.00    |
| GOOG   | 50     | $2,800.00  |

## Notes

- Earnings season starts next week.
- See the [shareholder letter](https://example.com/letter) for context.

> The market always overreacts in the short term.

```python
def total(positions):
    return sum(s * p for s, p in positions)
```

What the recipient sees:

  • Gmail / Outlook / Apple Mail: rendered <h1>, <h2>, a styled <table>, a clickable <a> link, a <blockquote> with a left border, and a <pre><code> block with monospace background.
  • Text-only client: the Markdown source verbatim — readable, with hierarchy preserved by # and ##, the table cells visible as pipes-and-dashes.

What gets stored in sent/alice/2026-05-07-120000-daily-briefing-2026-05-07.md:

+++
id = "2026-05-07-120000-daily-briefing-2026-05-07"
# ... (other frontmatter fields) ...
outbound = true
outbound_format = "markdown"
delivery_status = "delivered"
+++

# Daily briefing — 2026-05-07
# ... (Markdown source verbatim) ...

Re-running the daemon’s renderer on the stored body reproduces the recipient’s HTML view exactly.

Ready to try AIMX?

One command, one box, one inbox.

Get started