Appearance
Email Service
Cora uses two distinct email channels:
| Channel | Provider | Purpose |
|---|---|---|
| Campaign emails | Gmail API (OAuth) | Outbound sales sequences — sent via connected sender accounts |
| Transactional emails | Resend | Auth emails (verification, password reset, 2FA) |
Campaign Emails — Gmail OAuth
Campaign emails are sent through Gmail accounts connected by the brand. This provides:
- High deliverability (uses the sender's real Gmail account)
- Thread continuity (replies land in the same Gmail thread)
- Warmup management (gradual daily send limit increases)
Sender Account Flow
Warmup
New Gmail accounts start with a low daily send limit. The worker cron resets sendsToday = 0 each midnight UTC and progressively increases dailyLimit based on warmup schedules. Accounts in warmup are skipped if they've reached their current limit; the worker tries up to 3 accounts before rescheduling.
Reply Detection
Inbound Gmail replies are detected via webhook callbacks. When a reply is received:
CampaignLeads.hasRepliedis set totrue- All pending step jobs for that contact are skipped
- A
Conversationrecord captures the thread for the inbox view
Transactional Emails — Resend
Auth-related emails are sent via Resend using the RESEND_API_KEY env var. Templates are defined in packages/auth/src/emails.ts:
- Email address verification on sign-up
- Magic link / password reset
- Two-factor auth codes
Sender Account Data Model
The SenderAccount model (stored in MongoDB) tracks per-account state:
| Field | Purpose |
|---|---|
connectionStatus | VERIFIED / BROKEN / PENDING |
isActive | Whether the account can be used for sending |
dailyLimit | Current max sends per day (increases with warmup) |
sendsToday | Counter reset each midnight |
lastSendAt | Timestamp of last outbound message |
accessToken / refreshToken | Gmail OAuth credentials |