Skip to content

Conversation

@link2xt
Copy link
Contributor

@link2xt link2xt commented Jan 27, 2026

Moved this out of the main branch so we don't have AI slop about "highly specialized path designed to minimize battery drain and maximize privacy" on the readme

@link2xt
Copy link
Contributor Author

link2xt commented Jan 27, 2026

If anyone needs docs on how it works, I just wrote down how push notifications work, can as well be reused:

  1. Delta Chat on Android obtains FCM push token and gives it to the Rust core library: https://github.com/deltachat/deltachat-android/blob/c45a47e53c889016b7ddcf0d5f2dc5cc7f4b4090/src/gplay/java/org/thoughtcrime/securesms/notifications/FcmReceiveService.java#L64-L65
  2. Push token is given to the Rust core library via dc_accounts_set_push_device_token:
    https://github.com/chatmail/core/blob/f0a12d493c0233baf31778ac6c8b0cf4e709d5cc/deltachat-ffi/deltachat.h#L3349-L3356
  3. Inside the IMAP loop push token is encrypted to the hardcoded public OpenPGP key: https://github.com/chatmail/core/blob/f0a12d493c0233baf31778ac6c8b0cf4e709d5cc/src/imap.rs#L1670-L1671
    Private key belongs to central gateway https://notifications.delta.chat/
  4. Encrypted token is given to the IMAP server via SETMETADATA "INBOX" (/private/devicetoken <encrypted-token-goes-here>) command.
  5. Token is received by IMAP METADATA handler of a chatmail relay (dovecot with custom METADATA handler) here:
    https://github.com/chatmail/relay/blob/65b660c41334c358027fe46989dd7797bed8126c/chatmaild/src/chatmaild/metadata.py#L116-L118
  6. chatmail relay gives remembered token to https://notifications.delta.chat/notify via HTTP POST request:
    https://github.com/chatmail/relay/blob/65b660c41334c358027fe46989dd7797bed8126c/chatmaild/src/chatmaild/notifier.py#L160
  7. The token is decrypted by notifications.delta.chat service and is then sent to FCM or APNS or UBPorts (ubuntu touch) API here:

    notifiers/src/server.rs

    Lines 274 to 297 in 1abd85c

    /// Notifies a single device with a visible notification.
    async fn notify_device(
    axum::extract::State(state): axum::extract::State<State>,
    mut device_token: String,
    ) -> Result<StatusCode, AppError> {
    // Decrypt the token if it is OpenPGP-encrypted.
    if let Some(openpgp_device_token) = device_token.strip_prefix("openpgp:") {
    match state.openpgp_decryptor().decrypt(openpgp_device_token) {
    Ok(decrypted_device_token) => {
    device_token = decrypted_device_token;
    }
    Err(err) => {
    error!("Failed to decrypt device token: {:#}.", err);
    let metrics = state.metrics();
    metrics.openpgp_decryption_failures_total.inc();
    // Return 410 Gone response so email server can remove the token.
    return Ok(StatusCode::GONE);
    }
    }
    }
    info!("Got direct notification for {device_token}.");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants