Skip to content

jmap: send hangs indefinitely when message has an attachment #79

@mickenordin

Description

@mickenordin

Problem

Sending an email via JMAP completes normally when the body is plain (no attachment), but hangs indefinitely if any attachment is included. No error is surfaced, no send-failed event fires, and the op-failed hook is never hit. The compose window closes (the sync part of send_message returns Ok), but the background send task blocks forever.

Found while testing the attachment-token flow in #78; pre-existing (that PR does not touch the JMAP send path).

Where to look

src-tauri/src/mail/jmap.rs::JmapConnection::send_email

The function does two HTTP round trips:

  1. POST /upload/{accountId} with Content-Type: message/rfc822 and the full raw MIME bytes as the body.
  2. A standard JMAP Email/import + EmailSubmission/set call using the returned blobId.

The attachment-less case is fast because the body is tiny (<2 KB). With a ~70 KB PNG embedded, the raw message is on the order of ~100 KB after base64 — still small, but enough that the behaviour diverges.

The reqwest::Client used has no timeout configured, so a stuck upload stays stuck indefinitely instead of returning an error.

Diagnostics needed

Before fixing, confirm where the hang is:

  1. Wrap the upload send().await in a tokio::time::timeout (30–60 s) so a hang becomes a visible error rather than silent. Surface it via the existing op-failed path.
  2. Add log::debug! breadcrumbs around the upload:
    • JMAP upload: start, {bytes} bytes to {url}
    • JMAP upload: request sent, awaiting response
    • JMAP upload: response received, status={status}, elapsed={ms}
      This will tell us whether the client can't flush the body, the server never responds, or the JSON response never arrives.

Possible root causes (speculation until logs are in)

  • Server-side: the JMAP provider may reject or silently drop an rfc822 upload of this size; the connection stays open but nothing is written back. A sane timeout fixes the UX either way.
  • reqwest keep-alive reuse: the same http client is used for both the auth/session discovery and the upload. If the prior connection is in a half-closed state the upload could stall. Confirmable by forcing a fresh connection for the upload.
  • Content-Length vs chunked: body(Vec<u8>) sets Content-Length correctly; unlikely but worth a packet capture if the above two don't explain it.

Acceptance

  • Sending a JMAP message with an attachment either succeeds or surfaces a concrete error within a reasonable timeout window (default request timeout on the JMAP client, e.g. 60 s).
  • Log breadcrumbs around the upload make future reports of this shape diagnosable from the log alone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions