How the Gmail API Is Integrated for Unified Inbox and Email Service in Macro

The Macro email_service crate implements a full‑stack Gmail API integration using a layered architecture that handles OAuth token rotation, JWT verification via cached public keys, and bidirectional sync between Gmail threads and PostgreSQL to power a unified inbox.

The Macro repository (macro-inc/macro) delivers a unified email experience by synchronizing Gmail data into its own PostgreSQL schema. This Gmail API integration centers on the email_service crate, which wraps Google's REST endpoints in a Rust client that manages authentication, message ingestion, and label operations while maintaining data consistency across services.

OAuth Token Management and JWT Verification

Authentication begins with Redis-backed storage of OAuth refresh tokens. When the system needs to call Gmail, refresh_access_token in outbound/email_api/token_source.rs retrieves the stored refresh token, exchanges it for a short‑lived access token, and returns the bearer string. This token is automatically injected into every GmailClient request.

For JWT verification, the service caches Google's public keys in Redis. The get_google_public_keys function in util/gmail/auth.rs checks the cache first; if keys are missing, it calls GmailClient::get_google_public_keys, stores the result in Redis, and returns the KeyMap for signature validation.

use email_service::outbound::email_api::token_source::refresh_access_token;

async fn get_token(user_id: i64) -> anyhow::Result<String> {
    // `refresh_access_token` looks up the stored refresh token in Redis,
    // exchanges it for an access token, and returns the raw bearer string.
    let token = refresh_access_token(user_id).await?;
    Ok(token)
}
use std::sync::Arc;
use crate::util::redis::RedisClient;
use gmail_client::GmailClient;

async fn obtain_keys(redis: Arc<RedisClient>, gmail: Arc<GmailClient>) -> anyhow::Result<()> {
    // Returns a `KeyMap` containing the public keys
    let keys = email_service::util::gmail::auth::get_google_public_keys(redis, gmail).await?;
    println!("Fetched {} Google keys", keys.keys.len());
    Ok(())
}

Gmail Client Wrapper and REST Abstraction

The gmail_client crate provides a thin Rust façade over Gmail's REST endpoints using typed request/response models from models_email::gmail::*. The email_service crate's util/gmail/mod.rs exposes helper functions for creating a GmailClient instance pre‑configured with the current access token. All HTTP calls to Gmail—including message fetching, thread listing, and label retrieval—flow through this client.

Outbound mail transmission occurs via util/gmail/send.rs, which translates internal message structures into Gmail API format and handles the authenticated POST request. This abstraction ensures that token management, retry logic, and error mapping remain decoupled from business logic.

Unified Inbox Synchronization Pipeline

The sync pipeline consists of a worker in pubsub/inbox_sync/worker.rs that orchestrates sync jobs across users. The core logic resides in pubsub/inbox_sync/process.rs, where sync_user_inbox executes the fetch‑upsert cycle:

  1. Calls GmailClient::list_messages and GmailClient::get_message to retrieve raw Gmail data
  2. Transforms responses into internal EmailMessage structs
  3. Upserts records into the email_message PostgreSQL table via email_db_client
  4. Updates join tables to reflect thread and label relationships

After ingestion, the unified inbox view joins these rows with email_thread and email_labels tables, enabling the web client to query a single search service containing both Gmail and non‑Gmail messages.

use email_service::pubsub::inbox_sync::process::sync_user_inbox;

async fn run_sync(user_id: i64) -> anyhow::Result<()> {
    // Pulls messages from Gmail, inserts/updates them in the local DB,
    // and updates label mappings.
    sync_user_inbox(user_id).await?;
    Ok(())
}

Label Operations and Error Resilience

When users archive, delete, or move conversations, the system translates Macro label IDs into Gmail label IDs. The modify_message_labels function in pubsub/gmail_ops/operations/modify_message_labels.rs sends PATCH requests to GmailClient::modify_message, while delete_label in pubsub/gmail_ops/operations/delete_label.rs handles label removal.

Errors from Gmail are captured in pubsub/gmail_ops/email_api_error.rs and wrapped in internal error types. The system applies exponential back‑off retry policies for transient failures, ensuring that label state eventually converges even during API rate limits or network interruptions.

use email_service::pubsub::gmail_ops::operations::modify_message_labels;

async fn archive_thread(user_id: i64, gmail_msg_id: &str) -> anyhow::Result<()> {
    // Removes the "INBOX" label and adds the "ARCHIVE" label in Gmail.
    modify_message_labels(user_id, gmail_msg_id, vec!["INBOX"], vec!["ARCHIVE"]).await?;
    Ok(())
}

Summary

Frequently Asked Questions

How does Macro handle Gmail OAuth token expiration?

The refresh_access_token function in outbound/email_api/token_source.rs reads the encrypted refresh token from Redis and exchanges it with Google's OAuth endpoint for a new access token every time a Gmail API call is initiated. This ensures tokens never expire mid‑request while minimizing the exposure of long‑lived credentials.

What database tables support the unified inbox view?

The unified inbox relies on three primary PostgreSQL tables: email_message stores individual messages synced from Gmail, email_thread aggregates conversations across providers, and email_labels maintains the label taxonomy. The sync process in pubsub/inbox_sync/process.rs upserts data into these tables to maintain a consistent queryable state.

How are Gmail label changes synchronized bidirectionally?

When a user modifies a thread in Macro's UI, the system calls modify_message_labels in pubsub/gmail_ops/operations/modify_message_labels.rs, which translates internal label identifiers to Gmail's label IDs and issues a PATCH request via GmailClient. Conversely, during inbox sync, the worker pulls the latest label state from Gmail and updates the local email_labels mappings to reflect external changes.

What error handling strategy does the Gmail integration use?

Gmail API errors are intercepted in pubsub/gmail_ops/email_api_error.rs and mapped to internal error variants that support retry classification. Transient errors trigger exponential back‑off retries, while permanent failures are logged and surfaced to the user. This strategy ensures that temporary Gmail API outages do not corrupt the unified inbox state.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →