//! Deterministic email ID generation using UUID v5. //! //! Emails synced from IMAP/JMAP get a UUID derived from their Message-ID header, //! so the same email produces the same ID on every device. Manually created emails //! (no Message-ID) fall back to random UUIDs. use crate::id_types::EmailId; use uuid::Uuid; /// Fixed namespace UUID for GoingsOn email IDs (generated once, never changes). pub const GOINGSON_EMAIL_NS: Uuid = Uuid::from_bytes([ 0x7a, 0x3b, 0x8c, 0x2d, 0x4e, 0x5f, 0x6a, 0x1b, 0x9c, 0x0d, 0x8e, 0x7f, 0xa2, 0xb3, 0xc4, 0xd5, ]); /// Generate a deterministic email ID from a Message-ID header. /// /// - `Some(message_id)` → UUID v5 from namespace + message_id bytes /// - `None` → random UUID v4 (for manually created emails without Message-ID) pub fn deterministic_email_id(message_id: Option<&str>) -> EmailId { match message_id { Some(mid) => EmailId::from(Uuid::new_v5(&GOINGSON_EMAIL_NS, mid.as_bytes())), None => EmailId::new(), } } #[cfg(test)] mod tests { use super::*; #[test] fn same_message_id_same_uuid() { let a = deterministic_email_id(Some("")); let b = deterministic_email_id(Some("")); assert_eq!(a, b); } #[test] fn different_message_ids_different_uuids() { let a = deterministic_email_id(Some("")); let b = deterministic_email_id(Some("")); assert_ne!(a, b); } #[test] fn none_gives_random_uuid() { let a = deterministic_email_id(None); let b = deterministic_email_id(None); assert_ne!(a, b); } #[test] fn v5_uuid_version() { let id = deterministic_email_id(Some("")); assert_eq!(id.get_version_num(), 5); } }