OXYGENHUB

Mail reads [OxygenHub], in a wider message (closes #334) 262ec9c51a5c

The subject's tag and the footer use the From address's display name
(OxygenHub <noreply@...>), falling back to the site's name, which stays
as it is because it also labels the apt repository. The card is 720px
wide rather than 560.
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 8d77a9733267
When
· 2026-10-26 12:08 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
262ec9c51a5c37c68b17e6e78ad6c5583ee8f7a0b8debdc9d81b1454e878358f
Depends on
27babfa16580 34beb1a7e376 3556d08e17f9 a7e6e8e6094c b5668108487f cd46e56b1709
Depended on by
34388e612963
In bookmarks
api-unknown-fields diffusion-pin-bookmark main proposal-list-fast
In tags
v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.7 v0.9.8 v0.9.9
Edits
7 atoms, 0 tree operations, 2 files

Browse the tree at this change

modified CHANGELOG.md +1 −1
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 -
7 +
- **Mail reads `[OxygenHub]`, in a wider message (#334).** A notification's subject was tagged with the site's name (`[Oxygenhub on Earth]`); mail now goes by the From address's display name (`OxygenHub <noreply@…>`) in the subject and the footer, falling back to the site's name, so the site keeps its own name (which its apt repository is labelled with). The message card is 720px wide rather than 560.
8 8
- **An agent follows its owner's newest identity (oxygen#82).** A keyless certificate's answer now carries `author_chain`: the grant owner's newest linked identity and its predecessors back to the grant's key, so an agent home with `author.for` authors as the owner's current identity, not one several rotations old.
9 9
- **The profile page's actions are one tidy group (#328).** Edit profile, Projects and Drops sat on two rows, touching each other and the panel below; they are now one evenly spaced block, Edit profile across the top and the two pages side by side.
10 10
…
347 unchanged lines
modified crates/hub/src/mail.rs +26 −2
…
92 unchanged lines
93 93
    unsubscribe: Option<&str>,
94 94
) -> Result<(), String> {
95 95
    let cfg = settings(app);
96 -
    let site = app.config.site.name.clone();
96 +
    let site = sender_name(&cfg.from).unwrap_or_else(|| app.config.site.name.clone());
97 97
    let html = html(&site, app.config.base_url(), subject, body, action, unsubscribe);
98 98
    let subject = format!("[{site}] {subject}");
99 99
    use lettre::message::header::ContentType;
…
24 unchanged lines
124 124
    transport.send(message).await.map(|_| ()).map_err(|e| format!("sending to {to_addr}: {e}"))
125 125
}
126 126
127 +
/// The name mail goes by: the From address's display name
128 +
/// (`OxygenHub <noreply@…>`), in the subject's tag and the footer, so a
129 +
/// site's mail can read `[OxygenHub]` while the site keeps its own name
130 +
/// (which also names its apt repository, where a change makes every
131 +
/// `apt update` ask). `None` without one.
132 +
fn sender_name(from: &str) -> Option<String> {
133 +
    let (name, _) = from.split_once('<')?;
134 +
    let name = name.trim().trim_matches('"').trim();
135 +
    (!name.is_empty()).then(|| name.to_string())
136 +
}
137 +
127 138
/// The Oxygen mark, sent inline with each message (`cid:logo`), so it
128 139
/// shows without loading anything from the web.
129 140
const LOGO: &[u8] = include_bytes!("../../../assets/icons/oxygenhub-mail.png");
…
77 unchanged lines
207 218
<body style="margin:0;padding:0;background-color:#eef2f8;" class="ox-ground">
208 219
<div style="display:none;max-height:0;overflow:hidden;">{subject_e}</div>
209 220
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#eef2f8;" class="ox-ground"><tr><td align="center" style="padding:32px 12px;">
210 -
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:560px;background-color:#ffffff;border:1px solid #dfe5ee;border-radius:14px;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;" class="ox-card">
221 +
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:720px;background-color:#ffffff;border:1px solid #dfe5ee;border-radius:14px;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;" class="ox-card">
211 222
<tr><td style="background-color:#070c18;padding:20px 32px;" class="ox-pad">
212 223
<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr>
213 224
<td style="vertical-align:middle;"><a href="{base}"><img src="cid:logo" width="40" height="40" alt="" style="display:block;border:0;border-radius:9px;"></a></td>
…
308 unchanged lines
522 533
        assert_eq!(relay, "smtp://localhost:25?tls=none");
523 534
        assert_eq!(describe("file:///tmp/mail").service, "url");
524 535
        assert_eq!(describe("").service, "off");
536 +
    }
537 +
}
538 +
539 +
#[cfg(test)]
540 +
mod tests {
541 +
    use super::*;
542 +
543 +
    #[test]
544 +
    fn mail_goes_by_the_from_address_s_name() {
545 +
        assert_eq!(sender_name("OxygenHub <[email protected]>").as_deref(), Some("OxygenHub"));
546 +
        assert_eq!(sender_name("\"Oxygen Hub\" <[email protected]>").as_deref(), Some("Oxygen Hub"));
547 +
        assert_eq!(sender_name("[email protected]"), None);
548 +
        assert_eq!(sender_name(" <[email protected]>"), None);
525 549
    }
526 550
}