Skip to content

calternal-plugin-mail

Mail Plugin: encrypted provider accounts and a rebuildable mailbox projection.

The provider remains the source of truth. Account credentials are Security state in the Index; synced messages and cursors are derived from the provider and can be rebuilt. imap owns safe transport and protocol handling, sync owns resumable generation policy, and cache::store commits message windows and their cursor together. #726 prepares HTML at cache time through html; remote owns bounded image prefetch and derived-content jobs. proxy supplies owner-bound virtual UIDs and a separate exact MIME cache to the shared IMAP stack. proxy_mutations delivers accepted flags and selective deletion through durable Jobs. proxy_transfers journals COPY/MOVE receipts and preserves virtual UIDs while offline (#486, DESIGN §53).

Source: crates/plugins/mail/src/lib.rs

pub struct Credential

One provider app password. Debug output never reveals either field.

Implements: Clone, Serialize, Deserialize, fmt::Debug

pub fn new(username: String, app_password: String) -> Self

No doc comment.

pub fn with_smtp(
username: String,
app_password: String,
smtp_username: String,
smtp_app_password: String,
) -> Self

No doc comment.

pub fn username(&self) -> &str

No doc comment.

pub fn app_password(&self) -> &str

No doc comment.

pub fn smtp_username(&self) -> &str

No doc comment.

pub fn smtp_app_password(&self) -> &str

No doc comment.

Source: crates/plugins/mail/src/crypto.rs:20

pub struct EncryptedCredential

One encrypted app password stored with an account row.

Fields

  • pub nonce: Vec<u8>
  • pub ciphertext: Vec<u8>

Implements: Clone, fmt::Debug

Source: crates/plugins/mail/src/crypto.rs:76

pub struct InstanceKey(XChaCha20Poly1305, IntegrationKey);

Instance AEAD key. Debug output never includes key bytes.

Implements: Clone, fmt::Debug

pub fn new(key: [u8; 32]) -> Result<Self, CredentialError>

Build legacy and shared ciphers from one nonzero instance key. The two domains stay separate so image rollback can read old rows (§49 I6, #407).

pub fn encrypt(
&self,
owner_id: &str,
account_id: &str,
credential: &Credential,
) -> Result<EncryptedCredential, CredentialError>

Write a legacy Mail envelope for an unlinked account. Random nonces and owner/account AAD prevent reuse across rows (§49 I6, #407). Encrypt the Mail credential JSON with a fresh 24-byte nonce (#930). Authenticated data binds the User and Connected Account IDs in the Mail credential domain. Store nonce and ciphertext together; never substitute another account’s context (DESIGN §45).

pub fn decrypt(
&self,
owner_id: &str,
account_id: &str,
encrypted: &EncryptedCredential,
) -> Result<Credential, CredentialError>

Read the original Mail envelope for migration or an unlinked account. Persisted route consumers use decrypt_account instead (§49 I6, #407). Authenticate the stored envelope under the original User and Connected Account IDs (#930; DESIGN §45). Reject a nonce that is not 24 bytes. Only authenticated plaintext is decoded as credential JSON; altered bytes or a different account context return an authentication error.

pub fn decrypt_integration(
&self,
owner_id: &str,
account_id: &str,
encrypted: &EncryptedIntegrationCredential,
) -> Result<IntegrationCredential, IntegrationCredentialError>

Decrypt the one credential shared by linked Mail and Calendar services.

Source: crates/plugins/mail/src/crypto.rs:89

pub struct IntegratedAccountWrite<'a>

Mail connection settings for one shared account (#407, DESIGN §49).

Fields

  • pub owner_id: &'a str
  • pub id: &'a str
  • pub email: &'a str
  • pub provider: &'a str
  • pub imap_host: &'a str
  • pub imap_port: u16
  • pub imap_tls_mode: MailTlsMode
  • pub username: &'a str
  • pub smtp_host: &'a str
  • pub smtp_port: u16
  • pub smtp_tls_mode: MailTlsMode
  • pub smtp_username: &'a str
  • pub enabled: bool
  • pub created_ms: i64

Source: crates/plugins/mail/src/cache/store.rs:93

pub struct MailCreateAccount

No doc comment.

Fields

  • pub provider: MailProvider
  • pub email: String
  • pub imap_host: String
  • pub imap_port: u16
  • pub imap_tls_mode: MailTlsMode
  • pub username: String
  • pub app_password: String
  • pub smtp_host: String
  • pub smtp_port: u16
  • pub smtp_tls_mode: MailTlsMode
  • pub smtp_username: String
  • pub smtp_app_password: Option<String>

Implements: Clone, Deserialize, ToSchema, fmt::Debug

Source: crates/plugins/mail/src/routes.rs:385

pub struct MailProxyStore

The authenticated listener supplies this User; commands cannot replace it.

Implements: NotesStore

pub fn new(db: Db, owner: &str, key: InstanceKey) -> Self

Bind a cache and its encrypted upstream resolver to one authenticated User.

Source: crates/plugins/mail/src/proxy.rs:65

pub struct MessagePage

No doc comment.

Fields

  • pub messages: Vec<MessageSummary>
  • pub next: Option<PageCursor>

Source: crates/plugins/mail/src/cache/store.rs:316

pub struct MessageSummary

No doc comment.

Fields

  • pub id: String
  • pub account_id: String
  • pub folder_id: String
  • pub subject: String
  • pub from: Vec<serde_json::Value>
  • pub to: Vec<serde_json::Value>
  • pub cc: Vec<serde_json::Value>
  • pub bcc: Vec<serde_json::Value>
  • pub sent_date_ms: Option<i64>
  • pub received_ms: i64
  • pub preview: String
  • pub category: String
  • pub thread_id: String
  • pub body_truncated: bool
  • pub attachment_count: u32
  • pub flags: Vec<String>
  • pub labels: Vec<String>

Implements: Clone, Debug

Source: crates/plugins/mail/src/cache/store.rs:261

pub struct PageCursor

Stable keyset cursor for list pages.

Fields

  • pub received_ms: i64
  • pub id: String

Implements: Clone, Debug

Source: crates/plugins/mail/src/cache/store.rs:255

pub enum MailProvider

No doc comment.

Variants

  • Gmail
  • Icloud
  • Fastmail
  • Custom

Implements: Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema

Source: crates/plugins/mail/src/routes.rs:365

pub enum MailTlsMode

IMAP transport mode. Cleartext authentication is never allowed.

Variants

  • Implicit
  • Starttls

Implements: Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema

pub fn as_str(self) -> &'static str

Return the stable database value for this transport mode (#407, DESIGN §49).

Source: crates/plugins/mail/src/cache/store.rs:31

pub fn configure_instance_secret_key(key: [u8; 32]) -> Result<(), crypto::CredentialError>

Install the instance secret already loaded by the server for Calendar. Mail uses an independent AEAD domain while sharing the Instance key.

Source: crates/plugins/mail/src/lib.rs:69

pub async fn create_integrated_account(
transaction: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
account: IntegratedAccountWrite<'_>,
) -> Result<(), sqlx::Error>

Add Mail’s projection in the shared account transaction (#407, DESIGN §49). Sync decrypts the shared row; zero-length ciphertext is a legacy placeholder.

Source: crates/plugins/mail/src/cache/store.rs:461

pub fn device_store(
db: calternal_db::Db,
owner: &str,
) -> Result<MailProxyStore, crypto::CredentialError>

Construct an authenticated Mail reader with the server-loaded Instance key. The caller supplies the authenticated User, never a protocol parameter (#486, DESIGN §53). An unconfigured key fails before any provider access.

Source: crates/plugins/mail/src/lib.rs:81

pub async fn list_messages(
db: &Db,
owner_id: &str,
folder_id: &str,
category: Option<&str>,
before: Option<&PageCursor>,
limit: usize,
) -> Result<MessagePage, sqlx::Error>

Page one row per live message in a folder, even when several provider UIDs point at it. A covering membership index selects only the requested keyset window; details load for those keys in the same snapshot (#825, #626, DESIGN §45). While intent is queued, include its bounded destinations and pick one unread-first row across real and pending duplicate UIDs (#486).

Source: crates/plugins/mail/src/cache/store.rs:2167

pub fn migrations() -> calternal_db::MigrationSet

Create the Mail tables in the Index.

Source: crates/plugins/mail/src/cache/store.rs:322

pub async fn queue_integration_sync(
db: &calternal_db::Db,
owner_id: &str,
account_id: &str,
) -> Result<(), calternal_db::DbError>

Queue the first Mail sync for a newly enabled account (#407, DESIGN §49).

Source: crates/plugins/mail/src/lib.rs:51

pub fn rewrap_legacy_credential(
owner_id: &str,
account_id: &str,
encrypted: &EncryptedCredential,
instance_key: &[u8; 32],
integration_key: &IntegrationKey,
) -> Result<EncryptedIntegrationCredential, CredentialError>

Re-wrap one pre-Connected-Accounts Mail secret without exposing it to logs. The old Mail AAD and the shared Integration AAD differ, so copy the exact Mail and SMTP fields through memory and encrypt them under the shared domain before the account migration commits (#407, DESIGN §49 I6).

Source: crates/plugins/mail/src/crypto.rs:245

pub async fn test_integration_connection(input: &MailCreateAccount) -> Result<usize, String>

Check Mail settings and authenticate one IMAP connection before the server stores a shared Connected Account.

Source: crates/plugins/mail/src/lib.rs:39

pub async fn validate_integration_endpoint(host: &str, port: u16) -> Result<(), String>

Reject a provider host that resolves to a local or reserved network (#407, DESIGN §49).

Source: crates/plugins/mail/src/lib.rs:44