How Macro's Contact Management System Works and Syncs with CRM Data

Macro's contact management system stores contacts in a central CRM database and keeps them synchronized through an asynchronous SQS pipeline driven by the email service and the teams crate.

This article explains the architecture and data flow behind contact synchronization in the Macro codebase. The system ensures that every contact extracted from email activity is consistently reflected in the central CRM store, enabling unified contact management across the platform.


Core Architecture Overview

The contact management system spans multiple services and crates. The primary components include the email service for ingestion, the teams crate for queuing logic, and the CRM service for persistent storage.

Layer Primary Crate/Service Responsibility
Ingestion services/email_service Extracts contact data from email activity
Queuing crates/teams Builds and dispatches contact sync jobs
Persistence crm (via services/mcp_service) Maintains source-of-truth contact records
Querying apps/web Serves contact data to the frontend

Contact Ingestion from Email Activity

When a user sends or receives an email, the email service extracts address information and enqueues a contact-upsert job. This is handled by crm::domain::service::CrmService::populate_contact as implemented in services/mcp_service/src/context.rs.

The job is placed on the contacts SQS queue (email-service-crm-cleanup-queue), where workers in the email service process it asynchronously.

// Conceptual flow based on services/mcp_service/src/context.rs (lines 171-173)
// The CRM service is instantiated here and made available to workers
let crm_service = CrmServiceImpl::new(db_pool, config);
context.set_crm_service(Arc::new(crm_service));

The Contacts Enqueuer in the Teams Crate

The teams crate contains the core logic for creating SQS messages when contacts need synchronization. This operates through two primary modules.

Outbound Enqueuer

crates/teams/src/outbound/contacts_enqueuer.rs builds the payload and sends it to the contacts queue. This module handles the actual AWS SQS interaction.

Domain Logic

crates/teams/src/domain/contacts_enqueuer.rs determines when a contact should be synced. Trigger conditions include:

  • New email message received
  • Draft email sent
  • Explicit user-triggered sync

This separation of concerns allows the domain layer to define business rules while the outbound layer handles infrastructure concerns.


CRM Synchronization Service

The CRM crate provides the single source of truth for contact data. The implementation in crm::domain::service::CrmServiceImpl exposes several key methods:

  • populate_contact — Creates or updates a contact record
  • get_team_id_for_user — Resolves team context for contact association
  • upsert_contact — Idempotent contact write operation

When the enqueuer processes a contact, it invokes crm_service.populate_contact, which writes to the crm_contacts table and maintains the crm_contact_sources foreign-key relationship to track the originating email link.


Frontend Contact Queries

The web application fetches contacts through GraphQL queries that delegate to the CRM client. Two primary query modules handle this:

apps/web/src/lib/queries/crm/contacts.ts — Primary GraphQL interface for CRM-backed contacts

apps/web/src/lib/queries/contacts/contacts.ts — Alternative query interface used by specific UI components

These queries return normalized contact objects with fields including name, email, and photoUrl.


Complete Data Flow

Step Component Action
1 Email Service (services/email_service) Extracts contact info from sent/received email
2 SQS Contacts Queue Holds populate_contact job payload
3 Contacts Enqueuer (crates/teams/src/outbound/contacts_enqueuer.rs) Processes queue, calls CRM service
4 CRM Service (crm::domain::service::CrmServiceImpl) Persists to crm_contacts and crm_contact_sources tables
5 Web App (apps/web) GraphQL queries serve synced contacts to users

Key Source Files Reference

File Path Role
crates/teams/src/outbound/contacts_enqueuer.rs SQS message construction and dispatch
crates/teams/src/domain/contacts_enqueuer.rs Sync trigger logic and business rules
services/mcp_service/src/context.rs CRM service instantiation (lines 171-173)
services/email_service/src/pubsub/crm_cleanup/* Queue workers consuming contact jobs
apps/web/src/lib/queries/crm/contacts.ts Frontend GraphQL queries for CRM contacts
apps/web/src/lib/queries/contacts/contacts.ts Secondary frontend query interface
apps/docs/product/crm.mdx Product documentation for CRM features

Summary

  • Ingestion happens in the email service, which enqueues contact jobs to SQS
  • Queuing logic resides in the teams crate, with clear separation between domain rules and outbound infrastructure
  • Persistence is handled by the CRM service, which maintains relational data in crm_contacts with source tracking via crm_contact_sources
  • Queries are served through GraphQL endpoints consumed by the web application
  • All contact data lives in the CRM database and is kept synchronized through this asynchronous pipeline

Frequently Asked Questions

How does Macro decide when to sync a contact?

The domain logic in crates/teams/src/domain/contacts_enqueuer.rs evaluates multiple trigger conditions: new email messages, draft sends, and explicit user actions. This centralized decision logic prevents duplicate syncs and ensures contacts are only queued when meaningful changes occur.

Where is the CRM service instantiated?

The CRM service is instantiated in services/mcp_service/src/context.rs at lines 171-173, where CrmServiceImpl is created with database pool and configuration dependencies, then wrapped in an Arc for shared access across request handlers.

What database tables store contact information?

Contacts are stored in the crm_contacts table with source metadata tracked in crm_contact_sources. This foreign-key relationship enables auditing of how each contact entered the system and which email link originated the record.

Can the frontend query contacts directly from SQS?

No. The web application queries through GraphQL endpoints that hit the CRM service's database layer. The SQS queue is purely for asynchronous ingestion; it is not exposed to frontend clients.

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 →