Compare commits

..

3 Commits

Author SHA1 Message Date
01e1ca927e chore: Release
All checks were successful
Continuous integration / Test Suite (push) Successful in 44s
Continuous integration / Check (push) Successful in 2m0s
Continuous integration / Trunk (push) Successful in 40s
Continuous integration / Rustfmt (push) Successful in 1m0s
Continuous integration / build (push) Successful in 51s
Continuous integration / Disallow unused dependencies (push) Successful in 2m34s
2025-02-23 11:47:04 -08:00
1cc52d6c96 web: show X-Original-To: if To: is missing, fallback to Delivered-To: 2025-02-23 11:46:21 -08:00
e6b3a5b5a9 notmuch & server: plumb Delivered-To and X-Original-To headers 2025-02-23 09:37:09 -08:00
12 changed files with 164 additions and 12 deletions

10
Cargo.lock generated
View File

@ -2965,7 +2965,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-notmuch" name = "letterbox-notmuch"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"log", "log",
@ -2980,14 +2980,14 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-procmail2notmuch" name = "letterbox-procmail2notmuch"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
] ]
[[package]] [[package]]
name = "letterbox-server" name = "letterbox-server"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"anyhow", "anyhow",
@ -3030,7 +3030,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-shared" name = "letterbox-shared"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"build-info", "build-info",
"letterbox-notmuch", "letterbox-notmuch",
@ -3039,7 +3039,7 @@ dependencies = [
[[package]] [[package]]
name = "letterbox-web" name = "letterbox-web"
version = "0.8.5" version = "0.8.6"
dependencies = [ dependencies = [
"build-info", "build-info",
"build-info-build", "build-info-build",

View File

@ -8,7 +8,7 @@ authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2021" edition = "2021"
license = "UNLICENSED" license = "UNLICENSED"
publish = ["xinu"] publish = ["xinu"]
version = "0.8.5" version = "0.8.6"
repository = "https://git.z.xinu.tv/wathiede/letterbox" repository = "https://git.z.xinu.tv/wathiede/letterbox"
[profile.dev] [profile.dev]

View File

@ -271,6 +271,12 @@ pub struct Headers {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<String>, pub bcc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "Delivered-To")]
pub delivered_to: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "X-Original-To")]
pub x_original_to: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to: Option<String>, pub reply_to: Option<String>,
pub date: String, pub date: String,
} }
@ -639,6 +645,16 @@ impl Notmuch {
let hdr = &msg.headers.to; let hdr = &msg.headers.to;
if let Some(to) = hdr { if let Some(to) = hdr {
addrs.push(to); addrs.push(to);
} else {
let hdr = &msg.headers.x_original_to;
if let Some(to) = hdr {
addrs.push(to);
} else {
let hdr = &msg.headers.delivered_to;
if let Some(to) = hdr {
addrs.push(to);
};
};
}; };
let hdr = &msg.headers.cc; let hdr = &msg.headers.cc;
if let Some(cc) = hdr { if let Some(cc) = hdr {

View File

@ -48,8 +48,8 @@ urlencoding = "2.1.3"
#xtracing = { path = "../../xtracing" } #xtracing = { path = "../../xtracing" }
#xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" } #xtracing = { git = "http://git-private.h.xinu.tv/wathiede/xtracing.git" }
xtracing = { version = "0.3.0", registry = "xinu" } xtracing = { version = "0.3.0", registry = "xinu" }
letterbox-notmuch = { version = "0.8.5", path = "../notmuch", registry = "xinu" } letterbox-notmuch = { version = "0.8.6", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.8.5", path = "../shared", registry = "xinu" } letterbox-shared = { version = "0.8.6", path = "../shared", registry = "xinu" }
[build-dependencies] [build-dependencies]
build-info-build = "0.0.39" build-info-build = "0.0.39"

View File

@ -95,6 +95,10 @@ pub struct Message {
pub to: Vec<Email>, pub to: Vec<Email>,
// All CC headers found in email // All CC headers found in email
pub cc: Vec<Email>, pub cc: Vec<Email>,
// X-Original-To header found in email
pub x_original_to: Option<Email>,
// Delivered-To header found in email
pub delivered_to: Option<Email>,
// First Subject header found in email // First Subject header found in email
pub subject: Option<String>, pub subject: Option<String>,
// Parsed Date header, if found and valid // Parsed Date header, if found and valid

View File

@ -773,7 +773,19 @@ impl Query {
for uid in &self.uids { for uid in &self.uids {
parts.push(uid.clone()); parts.push(uid.clone());
} }
parts.extend(self.remainder.clone()); for r in &self.remainder {
// Rewrite "to:" to include ExtraTo:. ExtraTo: is configured in
// notmuch-config to index Delivered-To and X-Original-To headers.
if r.starts_with("to:") {
parts.push("(".to_string());
parts.push(r.to_string());
parts.push("OR".to_string());
parts.push(r.replace("to:", "ExtraTo:"));
parts.push(")".to_string());
} else {
parts.push(r.to_string());
}
}
parts.join(" ") parts.join(" ")
} }
} }

View File

@ -196,6 +196,8 @@ pub async fn thread(
let to = email_addresses(&path, &m, "to")?; let to = email_addresses(&path, &m, "to")?;
let cc = email_addresses(&path, &m, "cc")?; let cc = email_addresses(&path, &m, "cc")?;
let delivered_to = email_addresses(&path, &m, "delivered-to")?.pop();
let x_original_to = email_addresses(&path, &m, "x-original-to")?.pop();
let subject = m.headers.get_first_value("subject"); let subject = m.headers.get_first_value("subject");
let timestamp = m let timestamp = m
.headers .headers
@ -315,6 +317,8 @@ pub async fn thread(
body, body,
path, path,
attachments, attachments,
delivered_to,
x_original_to,
}); });
} }
messages.reverse(); messages.reverse();

View File

@ -12,5 +12,5 @@ version.workspace = true
[dependencies] [dependencies]
build-info = "0.0.39" build-info = "0.0.39"
letterbox-notmuch = { version = "0.8.5", path = "../notmuch", registry = "xinu" } letterbox-notmuch = { version = "0.8.6", path = "../notmuch", registry = "xinu" }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }

View File

@ -33,8 +33,8 @@ wasm-bindgen = "=0.2.100"
uuid = { version = "1.13.1", features = [ uuid = { version = "1.13.1", features = [
"js", "js",
] } # direct dep to set js feature, prevents Rng issues ] } # direct dep to set js feature, prevents Rng issues
letterbox-shared = { version = "0.8.5", path = "../shared", registry = "xinu" } letterbox-shared = { version = "0.8.6", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.8.5", path = "../notmuch", registry = "xinu" } letterbox-notmuch = { version = "0.8.6", path = "../notmuch", registry = "xinu" }
seed_hooks = { version = "0.4.0", registry = "xinu" } seed_hooks = { version = "0.4.0", registry = "xinu" }
[package.metadata.wasm-pack.profile.release] [package.metadata.wasm-pack.profile.release]

View File

@ -671,6 +671,30 @@
} }
} }
}, },
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "xOriginalTo",
"type": {
"kind": "OBJECT",
"name": "Email",
"ofType": null
}
},
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "deliveredTo",
"type": {
"kind": "OBJECT",
"name": "Email",
"ofType": null
}
},
{ {
"args": [], "args": [],
"deprecationReason": null, "deprecationReason": null,

View File

@ -31,6 +31,14 @@ query ShowThreadQuery($threadId: String!) {
name name
addr addr
} }
xOriginalTo {
name
addr
}
deliveredTo {
name
addr
}
timestamp timestamp
body { body {
__typename __typename

View File

@ -519,6 +519,48 @@ fn render_open_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Node<
}) })
] ]
]), ]),
IF!(msg.to.is_empty() && msg.x_original_to.is_some()=>div![
C!["text-xs"],
span![
C!["font-semibold"],
"Original To: "
],
span![
msg.x_original_to.as_ref().map(|to| {
let ShowThreadQueryThreadOnEmailThreadMessagesXOriginalTo { name, addr } = to;
span![
addr.as_ref().map(|addr| attrs! {
At::Title => addr
}),
name.as_ref().unwrap_or_else(|| addr.as_ref().unwrap_or(&unknown)),
" ",
addr.as_ref().map(|addr| copy_text_widget(&addr)),
" "
]
})
]
]),
IF!(msg.to.is_empty() && msg.x_original_to.is_none() && msg.delivered_to.is_some() => div![
C!["text-xs"],
span![
C!["font-semibold"],
"Delivered To: "
],
span![
msg.delivered_to.as_ref().map(|to| {
let ShowThreadQueryThreadOnEmailThreadMessagesDeliveredTo { name, addr } = to;
span![
addr.as_ref().map(|addr| attrs! {
At::Title => addr
}),
name.as_ref().unwrap_or_else(|| addr.as_ref().unwrap_or(&unknown)),
" ",
addr.as_ref().map(|addr| copy_text_widget(&addr)),
" "
]
})
]
]),
IF!(!msg.cc.is_empty() =>div![ IF!(!msg.cc.is_empty() =>div![
C!["text-xs"], C!["text-xs"],
span![ span![
@ -614,6 +656,48 @@ fn render_closed_header(msg: &ShowThreadQueryThreadOnEmailThreadMessages) -> Nod
], ],
" " " "
]), ]),
IF!(msg.to.is_empty() && msg.x_original_to.is_some()=>div![
C!["text-xs"],
span![
C!["font-semibold"],
"Original To: "
],
span![
msg.x_original_to.as_ref().map(|to| {
let ShowThreadQueryThreadOnEmailThreadMessagesXOriginalTo { name, addr } = to;
span![
addr.as_ref().map(|addr| attrs! {
At::Title => addr
}),
name.as_ref().unwrap_or_else(|| addr.as_ref().unwrap_or(&unknown)),
" ",
addr.as_ref().map(|addr| copy_text_widget(&addr)),
" "
]
})
]
]),
IF!(msg.to.is_empty() && msg.x_original_to.is_none() && msg.delivered_to.is_some() => div![
C!["text-xs"],
span![
C!["font-semibold"],
"Delivered To: "
],
span![
msg.delivered_to.as_ref().map(|to| {
let ShowThreadQueryThreadOnEmailThreadMessagesDeliveredTo { name, addr } = to;
span![
addr.as_ref().map(|addr| attrs! {
At::Title => addr
}),
name.as_ref().unwrap_or_else(|| addr.as_ref().unwrap_or(&unknown)),
" ",
addr.as_ref().map(|addr| copy_text_widget(&addr)),
" "
]
})
]
]),
IF!(!msg.cc.is_empty() => div![ IF!(!msg.cc.is_empty() => div![
C!["text-xs", "max-w-full", "overflow-clip", "text-ellipsis"], C!["text-xs", "max-w-full", "overflow-clip", "text-ellipsis"],
span![ span![