Skip to content

feat: add HTML email body and attachment support (#20)#21

Merged
radiosilence merged 3 commits intomainfrom
feat/html-email-attachments
Apr 11, 2026
Merged

feat: add HTML email body and attachment support (#20)#21
radiosilence merged 3 commits intomainfrom
feat/html-email-attachments

Conversation

@radiosilence
Copy link
Copy Markdown
Owner

Summary

  • HTML email body: --html-body (inline) and --html-file (from file) flags on send, reply, and forward. JMAP assembles multipart/alternative automatically when both text and HTML are provided.
  • File attachments: -a/--attachment flag (repeatable) on send, reply, and forward. Files uploaded via new upload_blob JMAP method, attached with proper multipart/mixed MIME tree using bodyStructure.
  • GraphQL mutations: sendEmail, replyToEmail, forwardEmail accept optional html_body parameter. Previews indicate when HTML is included.
  • Refactored body construction: single code path in create_and_submit_email handles plain text, text+HTML, and attachments. Extracted build_compose_params helper in main.rs to deduplicate CLI argument resolution across compose commands.

Closes #20

Test plan

  • cargo test — 40 tests pass
  • cargo clippy --workspace -- -D warnings — zero warnings
  • cargo fmt --check — clean
  • Manual: send email with --html-body flag
  • Manual: send email with --html-file flag
  • Manual: send email with -a attachment.pdf
  • Manual: send email with combined HTML + attachments
  • Manual: reply/forward with HTML and attachments

🤖 Generated with Claude Code

Send/reply/forward now support --html-body (inline), --html-file (from
file), and -a/--attachment (repeatable file attachments). JMAP body
construction handles plain text, multipart/alternative (text+HTML), and
multipart/mixed (with attachments) in a single code path. Adds
upload_blob method for posting raw bytes to Fastmail's upload endpoint.
GraphQL mutations accept optional html_body parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@radiosilence radiosilence left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid work. Three nits:

1. match ... { Ok => ..., Err(e) => Err(e) } × 3

This pattern in main.rs (lines 488-499, 539-550, 563-574) is a verbose identity on Err. Simplify with ?:

} => {
    let params = build_compose_params(
        cc.as_deref(),
        bcc.as_deref(),
        from.as_deref(),
        draft,
        html_body,
        html_file,
        &attachments,
    )?;
    commands::send(&to, &subject, &body, reply_to.as_deref(), params).await
}

Same for Reply and Forward.

2. upload_blob swallows 4xx errors

The catch-all _ => {} at jmap/mod.rs:980 lets 4xx errors (e.g. 413 payload too large, 400 bad request) fall through to the blobId parse, which fails with a confusing "Upload response missing blobId". Add:

400..=499 => {
    let status = resp.status();
    let text = resp.text().await.unwrap_or_default();
    return Err(Error::Server(format!("Upload failed ({}): {}", status, text)));
}

3. No unit tests for load_attachment / resolve_html

Both are simple but worth covering — especially resolve_html's three branches (inline, file, none) and load_attachment's MIME inference + error on missing file. The test module in util.rs is right there.

None of these are blocking, but worth a quick pass.

radiosilence and others added 2 commits April 11, 2026 16:28
…ml tests

- upload_blob: explicit 4xx error arm instead of swallowing to confusing
  "missing blobId" message
- Send/Reply/Forward: replace `match { Ok => ..., Err(e) => Err(e) }`
  with `async { ...? }.await`
- Add 7 unit tests for load_attachment and resolve_html (tempfile dev-dep)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract `apply_body_structure` pure function from `create_and_submit_email`
  for direct unit testing without async/network dependencies
- 6 body structure tests covering all JMAP modes:
  - plain text only (textBody array)
  - text + HTML (textBody + htmlBody arrays)
  - text + attachment (bodyStructure multipart/mixed)
  - HTML + attachment (bodyStructure with nested multipart/alternative)
  - multiple attachments
- 3 upload_blob tests via wiremock mock server:
  - success (200 with real Fastmail response shape)
  - 413 payload too large (verifies 4xx error handling)
  - 429 rate limited
- 55 total tests, all passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@radiosilence radiosilence merged commit 3ecd9a7 into main Apr 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: feat: add multipart HTML email and attachment support to compose commands

1 participant