Skip to content

Email & Messaging

What this module does

Agency staff communicate with clients and providers through multiple channels. This module handles:

  • Gmail and Outlook sync — bi-directional email sync that automatically tags emails to the right case and contact
  • Transactional email — notifications, form links, and system emails sent via SendGrid
  • In-app messaging — real-time chat via the Gurgle platform
  • SMS — text message notifications via Twilio

Business value

A case manager might send and receive hundreds of emails per case across the entire journey. Without email sync, staff have to manually copy-paste email content into Orchid to keep the case record complete. With email sync, every email automatically appears in the right case’s timeline, giving the whole team context without any manual work.

Key files

  • Directoryorchid/
    • Directorymodels/
      • email_sync.py EmailSync, EmailSyncStatus, EmailSyncType, EmailSyncDisconnectReason
      • general.py EmailMessage, Email, EmailTemplate, MassEmail
    • Directoryutils/
      • wrapper_gmail.py 36 KB — Gmail API wrapper
      • wrapper_microsoft.py 41 KB — Microsoft Graph API wrapper
      • email_matching.py 18 KB — email-to-case matching logic
      • email.py 30 KB — email sending utilities
      • email_preview.py email preview utilities
    • Directoryapi/
      • email.py 92 KB — email API (inbox, compose, reply, sync management)
    • Directoryviews/
      • email.py 44 KB — email inbox UI
      • email_templates.py email template management UI
    • Directorymodels/
      • general.py OAuthToken, OAuthTokenType (used for Gmail/Outlook OAuth)
  • Directoryorchid_jobs/
    • Directoryjobs/
      • email_sync.py background email sync jobs
      • email_matching.py background email-to-contact matching jobs

Architecture overview

sequenceDiagram
participant Admin as Agency Staff
participant App as flaskapp.py
participant Jobs as orchid_jobs (Huey)
participant Gmail as Gmail API
participant Outlook as Microsoft Graph
participant DB as MySQL
Admin->>App: Connect email account (OAuth)
App->>DB: Store OAuthToken
loop Periodic job (background)
Jobs->>DB: Get active EmailSync records
Jobs->>Gmail: Fetch new messages (since last sync)
Jobs->>Outlook: Fetch new messages (since last sync)
Jobs->>DB: Store as EmailMessage records
Jobs->>Jobs: Run email_matching job
Jobs->>DB: Link EmailMessage to Case/Contact
end
Admin->>App: Open email inbox (/email)
App->>DB: Load EmailMessage records for admin
App-->>Admin: Render inbox with case-linked emails

Email sync — how it works

Step 1: OAuth connection

When an admin connects their Gmail or Outlook account, an OAuth flow runs:

  1. Admin clicks “Connect Gmail” / “Connect Outlook”
  2. Orchid redirects to Google/Microsoft OAuth
  3. After authorization, an access token + refresh token are stored as an OAuthToken record
  4. An EmailSync record is created to track sync state for this admin

Step 2: Background sync (Huey job)

The email_sync job in orchid_jobs/jobs/email_sync.py runs periodically:

  1. Loads all active EmailSync records across agencies
  2. For each sync: calls wrapper_gmail.py or wrapper_microsoft.py to fetch messages since the last sync
  3. Stores each message as an EmailMessage record
  4. Updates EmailSync.last_synced_at

Step 3: Email matching (Huey job)

The email_matching job in orchid_jobs/jobs/email_matching.py runs after sync:

  1. Picks up newly stored EmailMessage records with no case/contact link
  2. Analyzes the email’s From/To addresses against known user emails in the database
  3. If a match is found → links EmailMessage to the corresponding Case or Contact
  4. Unmatched emails remain in the inbox unlinked — staff can manually link them

EmailSync model — tracking sync health

class EmailSync:
admin_id # which admin's email account
sync_type # 'gmail' or 'microsoft'
status # 'active', 'paused', 'error', 'disconnected'
last_synced_at # when was the last successful sync
disconnect_reason # EmailSyncDisconnectReason FK if disconnected

EmailSyncDisconnectReason records why a sync was disabled (token expired, admin manually disconnected, authentication error). Always check this when an admin reports their email sync stopped.

Two wrappers, one purpose

wrapper_gmail.py (36 KB) and wrapper_microsoft.py (41 KB) implement the same email-fetching functionality for two different email providers. There is no shared abstraction between them — they are independent implementations.

Each wrapper handles:

  • Token refresh (access tokens expire, refresh tokens renew them)
  • Fetching messages since a given timestamp
  • Parsing raw API responses into structured data
  • Error handling and rate limit handling

Transactional email — SendGrid

orchid/utils/email.py handles sending transactional emails via SendGrid:

  • Welcome emails to new users
  • Form assignment notifications
  • Password reset links
  • Case status update notifications
  • Mass email campaigns (via MassEmail model)

The EmailTemplate model stores reusable email template bodies. Templates use Jinja2 syntax and are rendered before sending.

In local mode (_LOCAL_MODE = True), SendGrid sending is disabled and email content is logged to the console instead.

In-app messaging — Gurgle

Gurgle is a separate messaging platform integrated into Orchid for real-time in-app chat between agency staff and clients.

Key integration points:

  • orchid/api/gurgle.py — API endpoints for sending/receiving Gurgle messages
  • orchid/views/gurgle.py — Gurgle inbox UI at /messaging
  • gurgleclient/ — the Gurgle client library (a git submodule)
  • static/js/pack_gurgleclient.mjs — Gurgle’s JavaScript client bundle

Gurgle messages are separate from emails and from the EmailMessage model. They live in the Gurgle service’s own storage, not in Orchid’s database.

SMS — Twilio

Twilio is used for SMS notifications. Configuration requires TWILIO_SID, TWILIO_AUTH_TOKEN, and TWILIO_NUMBER in the environment config. SMS is used sparingly — primarily for urgent case notifications.

Gotchas