Email Composer
Relevant source files
The following files were used as context for generating this wiki page:
- crates/mailiner-composer/src/lib.rs
- crates/mailiner-composer/src/model/draft.rs
- crates/mailiner-composer/src/export.rs
- crates/mailiner-composer/src/reply/prefill.rs
- crates/mailiner-app/src/components/compose.rs
- crates/mailiner-composer/src/reply/quote.rs
- crates/mailiner-html/src/lib.rs
- crates/mailiner-mime/src/writer/mod.rs
The Email Composer manages the lifecycle of an email draft from initialization and editing to MIME serialization for outbound transport. It provides specialized logic for plain and rich text editing, recipient management, attachment handling, and reply/forward prefill. The system separates the logical draft model from the UI components and the final message export orchestration.
Sources: crates/mailiner-composer/src/lib.rs:1-12, crates/mailiner-app/src/components/compose.rs:1-15
Core Data Model
The central entity is the DraftDocument, which holds all state for a single compose session. It tracks recipients, subject, body content in multiple modes, and attachments.
Draft Structure
| Field | Type | Description |
|---|---|---|
id | DraftId | Stable UUID v4 session identifier. |
to/cc/bcc | Vec<ComposerAddress> | Lists of recipient addresses. |
mode | BodyMode | Indicates if Plain or Rich (HTML) is authoritative. |
plain_body | String | Text content used for plain-text emails. |
html_body | String | HTML fragment used for rich-text emails. |
attachments | Vec<FileAttachment> | Non-inline file attachments. |
inline_images | Vec<InlineImage> | Images referenced via CID/Blob in HTML. |
Sources: crates/mailiner-composer/src/model/draft.rs:55-89, crates/mailiner-composer/src/model/draft.rs:24-34
Constraints and Caps
The composer enforces hard limits on draft payloads to ensure performance and compatibility.
| Cap Name | Value |
|---|---|
MAX_FILE_BYTES | 25 MiB |
MAX_DRAFT_BYTES | 40 MiB |
MAX_ATTACHMENTS | 20 files |
MAX_HTML_LEN | 1.5 MiB |
Sources: crates/mailiner-composer/src/model/draft.rs:125-142
Prefill and Reply Logic
The build_draft function initializes a DraftDocument based on a ComposeIntent. For replies and forwards, the system extracts information from an existing Envelope and LoadedMessage.
Threading and Attribution
When replying, the composer:
- Sets Threading Headers: Populates
In-Reply-Towith the original message ID and appends it to theReferenceschain. - Generates Attribution: Creates a line (e.g., "On [Date], [User] wrote:") using
attribution_line. - Quotes Content: Wraps the original body. Plain text receives
>markers, while HTML is wrapped in amlnr-compose-quotediv.
Sources: crates/mailiner-composer/src/reply/prefill.rs:50-100, crates/mailiner-composer/src/reply/quote.rs:9-35
The diagram above shows the logic flow for initializing a draft based on user intent.
Sources: crates/mailiner-composer/src/reply/prefill.rs:50-110
Validation and Sanitization
Before a draft can be exported or sent, it undergoes strict validation and HTML sanitization.
Validation Rules
validate_draft checks the following:
- Recipients: The
Tofield must not be empty. All emails must passis_valid_email_v1(requiring a local part,@, and a domain with a.). - Sizes: Checks if individual attachments or the total draft size exceed defined caps.
- State: Ensures all attachments have finished reading (not
Pending).
Sources: crates/mailiner-composer/src/model/draft.rs:240-300
HTML Sanitization Policies
The composer uses ammonia to enforce two different policies:
- Edit Policy: Used when injecting content into the editor. It allows
blob:,cid:, and safedata:images but strips remote images. - Export Policy: Used before MIME serialization. It allows only
cid:for images;blob:ordata:must be rewritten or stripped.
Sources: crates/mailiner-html/src/lib.rs:88-106, crates/mailiner-html/src/lib.rs:136-160
Export and MIME Serialization
The prepare_submit function transforms a DraftDocument into a PreparedMessage ready for SMTP.
MIME Tree Construction
The export process builds a nested MIME structure based on the draft content:
- Alternative: A
multipart/alternativepart containing bothtext/plainandtext/html. - Related: If inline images exist, the Alternative part is wrapped in
multipart/related. - Mixed: If file attachments exist, the tree is wrapped in
multipart/mixed.
MIME hierarchy showing how parts are nested when a message contains text, inline images, and attachments.
Sources: crates/mailiner-composer/src/export.rs:205-248
Serialization Details
The MIME writer handles RFC-compliant formatting:
- Headers: Encodes unstructured headers (like Subject) using RFC 2047.
- Folding: Folds long header lines to the preferred 78-character limit.
- Transfer Encoding: Uses
quoted-printablefor text andbase64for binary data. - Filenames: Applies RFC 2231 encoding for attachment filenames containing non-ASCII characters.
Sources: crates/mailiner-mime/src/writer/mod.rs:58-95, crates/mailiner-mime/src/writer/mod.rs:115-135
User Interface Integration
The ComposeOverlay component in the mailiner-app crate provides the Dioxus-based UI. It manages local signals for input fields and interacts with the application context to trigger sending via CoreEvent.
Sending Process
- User Action: Clicking "Send" triggers
prepare_submit. - Preparation: If validation passes, a
PreparedMessageis created containing therfc822bytes and aSubmitEnvelope. - Dispatch: The component sends a
CoreEvent::SendMessageto the backend.
Sources: crates/mailiner-app/src/components/compose.rs:300-340