Load draft bodies and stop spamming "No details for mail"

`load_mail_details_impl` now mirrors the TS `loadMailDetails` router:
when `mail.mailDetailsDraft` is set, call the new
`MailFacade::load_mail_details_draft` (sdk-mail-draft-details); when
`mail.mailDetails` is set, keep the existing blob path; otherwise return
`Ok(None)` — the legacy/malformed leaf the prefetch loop has to handle
anyway. Practical effect: the ~88 drafts on the test account that
previously logged "No details for mail" on every store change now decrypt
their body normally and surface it through IMAP FETCH.

The `Ok(None)` branch in `prefetch_details` was the secondary cause of
the log spam — with the prefetch loop now event-driven (Phase 2.5), every
`MailStore` bump re-queued the same 88 drafts because `has_eml` stayed
false. Persist a headers-only `.eml` and `mark_has_details = 1` on that
branch so the row is considered done; the next sweep skips it, and the
client at least gets the headers for a mail whose body we genuinely
cannot locate.
This commit is contained in:
Anthony
2026-05-28 15:51:14 +02:00
parent bb15bf744e
commit 4bb07798dd
2 changed files with 32 additions and 4 deletions
+14 -1
View File
@@ -615,7 +615,20 @@ async fn prefetch_details(
}
}
Ok(None) => {
debug!("No details for mail {:?}", mail.subject);
// The mail references neither a `MailDetailsBlob` nor a
// `MailDetailsDraft` (legacy / malformed). Persist a
// headers-only `.eml` and mark the row done so we don't
// re-attempt every prefetch sweep.
let rfc2822 = mail_to_rfc2822(mail, None);
if let Some(id) = mail._id.as_ref() {
let eid = id.element_id.to_string();
if let Err(e) = local_store.write_eml(&eid, &rfc2822) {
warn!("Failed to cache headers-only eml {}: {}", eid, e);
}
if let Err(e) = local_store.mark_has_details(&eid) {
warn!("Failed to mark has_details {}: {}", eid, e);
}
}
}
Err(e) => {
warn!("Failed to prefetch details for {:?}: {}", mail.subject, e);
+18 -3
View File
@@ -264,15 +264,30 @@ impl TutaSession {
&self,
mail: &Mail,
) -> Result<Option<MailDetails>, ApiCallError> {
if mail.mailDetails.is_some() {
match self.logged_in.mail_facade().load_mail_details_blob(mail).await {
// Tuta stores a mail's body in two different places depending on
// whether the mail is received or a draft. Match the TS routing
// (`isDraft(mail) ? loadMailDetailsDraft : loadMailDetailsBlob`).
let mail_facade = self.logged_in.mail_facade();
if mail.mailDetailsDraft.is_some() {
match mail_facade.load_mail_details_draft(mail).await {
Ok(details) => Ok(Some(details)),
Err(e) => {
log::error!("Failed to load mail details draft: {e}");
Err(e)
},
}
} else if mail.mailDetails.is_some() {
match mail_facade.load_mail_details_blob(mail).await {
Ok(details) => Ok(Some(details)),
Err(e) => {
log::error!("Failed to load mail details blob: {e}");
Err(e)
}
},
}
} else {
// Legacy mail without either reference — body lives nowhere we
// know how to read. The prefetch layer treats `Ok(None)` as
// "render with headers only, do not retry".
Ok(None)
}
}