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 emailsEmail sync — how it works
Step 1: OAuth connection
When an admin connects their Gmail or Outlook account, an OAuth flow runs:
- Admin clicks “Connect Gmail” / “Connect Outlook”
- Orchid redirects to Google/Microsoft OAuth
- After authorization, an access token + refresh token are stored as an
OAuthTokenrecord - An
EmailSyncrecord 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:
- Loads all active
EmailSyncrecords across agencies - For each sync: calls
wrapper_gmail.pyorwrapper_microsoft.pyto fetch messages since the last sync - Stores each message as an
EmailMessagerecord - 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:
- Picks up newly stored
EmailMessagerecords with no case/contact link - Analyzes the email’s From/To addresses against known user emails in the database
- If a match is found → links
EmailMessageto the correspondingCaseorContact - 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 disconnectedEmailSyncDisconnectReason 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
MassEmailmodel)
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 messagesorchid/views/gurgle.py— Gurgle inbox UI at/messaginggurgleclient/— 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.