Linking SendGrid as a source
Let AI connect your sources for you
Skip the manual setup — run this in your project and the wizard auto-detects your databases and APIs and connects them to PostHog.

This source is currently in alpha. The interface and available tables may change.
The SendGrid connector pulls your SendGrid data into the PostHog data warehouse, covering suppressions, unsubscribe groups, marketing lists, and email templates.
Prerequisites
You need a SendGrid account with permission to create API keys. Marketing lists additionally require an account with Marketing Campaigns, which is covered under required scopes below.
Adding a data source
- In PostHog, go to the Sources tab of the data pipeline section.
- Click + New source and click Link next to this source.
- Enter your credentials (see Configuration below) and click Next.
- Select the tables you want to sync, choose a sync method and frequency, then click Import.
Once the syncs are complete, you can start querying this data in PostHog.
The only credential this source needs is a SendGrid API key. Create one in your SendGrid API keys settings under Settings > API Keys. Give it Restricted Access and grant read access to the areas you want to sync:
- Suppressions for bounces, blocks, invalid emails, spam reports, global unsubscribes, and unsubscribe groups
- Marketing for marketing lists
- Template Engine for templates
The key value starts with SG. and SendGrid shows it only once, so copy it before closing the dialog.
Required scopes
SendGrid grants scopes per endpoint, so a key that reads one table often cannot read another. Each table needs the scope below, spelled as SendGrid's /v3/scopes endpoint reports it:
| Table | SendGrid scope |
|---|---|
bounces | suppression.bounces.read |
blocks | suppression.blocks.read |
invalid_emails | suppression.invalid_emails.read |
spam_reports | suppression.spam_reports.read |
global_unsubscribes | suppression.unsubscribes.read |
unsubscribe_groups | asm.groups.read |
marketing_lists | marketing.read |
templates | templates.read |
You don't have to grant every scope. When you paste the key, PostHog checks each table and flags the ones the key can't read, so you can connect with a narrow key and sync only what you need.
marketing_lists reads SendGrid's Marketing Campaigns API, so marketing.read alone isn't always enough. Accounts without Marketing Campaigns, and accounts still on legacy Marketing Campaigns, return a permission error for this table however the key is scoped. If that's your account, leave marketing_lists unselected. Every other table still syncs.
Sync modes
Each table can be synced in one of several modes, depending on what the source supports:
- Webhook (when available) – the source pushes changes to PostHog in real time. Fastest freshness, lowest ongoing cost, and the only mode that reliably captures updates and deletes.
- Incremental – only new or updated rows are synced on each run, using a cursor field (such as an
updated_attimestamp). Cheaper than a full refresh, but deletes aren't captured. - Append only – new rows are appended using a cursor field; existing rows are never updated. Ideal for immutable, append-only tables like event logs.
- Full refresh – the whole table is reloaded on every sync. Use it when a table has no reliable cursor or when you need deletions reflected.
See sync methods for a full explanation of how each mode works and how to choose between them.
The suppression tables filter server side on their immutable created timestamp, so they sync incrementally. The remaining tables have no timestamp filter in the SendGrid API and sync as a full refresh.
Configuration
| Option | Type | Required |
|---|---|---|
API key | password | Yes |
Supported tables
| Table | Description | Sync method | Incremental field | Primary key |
|---|---|---|---|---|
bounces | Email addresses that bounced — the receiving server rejected the message. | Incremental, Full refresh | created | — |
blocks | Email addresses blocked due to temporary delivery issues such as a full mailbox or server outage. | Incremental, Full refresh | created | — |
invalid_emails | Email addresses that are invalid or malformed and cannot receive mail. | Incremental, Full refresh | created | — |
spam_reports | Email addresses of recipients who marked your mail as spam. | Incremental, Full refresh | created | — |
global_unsubscribes | Email addresses on the global unsubscribe list, suppressed from all future mail. | Incremental, Full refresh | created | — |
stats | Global email statistics aggregated per day — the send-side volume (requests, delivered, opens, clicks) that turns suppression counts into deliverability rates. | Incremental, Full refresh | date | — |
unsubscribe_groups | Suppression (unsubscribe) groups used to let recipients opt out of specific categories of mail. | Full refresh | — | — |
marketing_lists | Marketing contact lists used to segment recipients for campaigns. | Full refresh | — | — |
templates | Email templates (legacy and dynamic) used to compose messages. | Full refresh | — | — |
message_activity | Per-message activity from the Email Activity feed: one row per sent message with its latest delivery status and engagement counts. Requires SendGrid's paid additional email activity history add-on. | Incremental, Full refresh | last_event_time | — |
Troubleshooting
A table is paused with a missing scope error
The key authenticated but isn't allowed to read that table, so adding the scope fixes it. You don't need to generate a new key:
- In SendGrid, go to Settings > API Keys and edit the existing key.
- Add read access for the table's area, using the required scopes table above.
- In PostHog, re-enable the sync for that table. A paused table stays paused until you turn it back on.
Allow a few minutes after updating a key before retrying, since SendGrid takes a moment to apply new permissions.
If only marketing_lists fails while the other tables sync, check whether your account has Marketing Campaigns before changing scopes. Without it, no scope grant will make that table sync.
The key is rejected when adding the source
A rejected key is invalid or expired rather than under-scoped. Generate a new key in SendGrid and paste the full value, including the SG. prefix.
If your sync is failing or data looks wrong, see the Data warehouse troubleshooting guide. If that doesn't help, contact support – we're happy to help.