> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Linking SendGrid as a source Canonical URL: https://posthog.com/docs/data-pipelines/sources/sendgrid > **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.posthog.com` instead of `https://us.posthog.com` or `https://app.posthog.com` (app host for links). ## On this page - [Prerequisites](#prerequisites) - [Adding a data source](#adding-a-data-source) - [Sync modes](#sync-modes) - [Configuration](#configuration) - [Supported tables](#supported-tables) - [Troubleshooting](#troubleshooting) ![](https://res.cloudinary.com/dmukukwp6/image/upload/texture_tan_9608fcca70) ![](https://res.cloudinary.com/dmukukwp6/image/upload/texture_tan_dark_a92b0e022d) 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. `npx @posthog/wizard warehouse` ![PostHog Wizard hedgehog](https://res.cloudinary.com/dmukukwp6/image/upload/wizard_3f8bb7a240.png) ![](https://res.cloudinary.com/dmukukwp6/image/upload/wizard_3f8bb7a240.png)Let AI connect your sources for you The SendGrid connector pulls your SendGrid data into the PostHog data warehouse, covering suppressions, daily email statistics, per-message activity, unsubscribe groups, unsubscribe group memberships, marketing lists, Single Sends, Single Send statistics, and email templates. ## Prerequisites You need a SendGrid account with permission to create API keys. Marketing tables additionally require an account with Marketing Campaigns, which is covered under [required scopes](#required-scopes) below. ## Adding a data source 1. In PostHog, go to the [Sources tab](https://app.posthog.com/data-management/sources) of the data pipeline section. 2. Click **\+ New source** and click **Link** next to this source. 3. Enter your credentials (see [Configuration](#configuration) below) and click **Next**. 4. 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](https://app.sendgrid.com/settings/api_keys) 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, unsubscribe groups, and unsubscribe group memberships - **Stats** for daily email statistics - **Marketing** for marketing lists, Single Sends, and Single Send statistics - **Template Engine** for templates - **Email Activity** for message activity 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` | | `stats` | `stats.read` | | `unsubscribe_groups` | `asm.groups.read` | | `unsubscribe_group_suppressions` | `asm.groups.read` | | `marketing_lists` | `marketing.read` | | `singlesends` | `marketing.read` | | `singlesend_stats` | `marketing.read` | | `singlesend_link_stats` | `marketing.read` | | `templates` | `templates.read` | | `message_activity` | `email_activity.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 tables need Marketing Campaigns** `marketing_lists`, `singlesends`, `singlesend_stats`, and `singlesend_link_stats` read 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 these tables however the key is scoped. If that's your account, leave these marketing tables unselected. Every other table still syncs. **Message activity needs the email activity add-on** `message_activity` reads SendGrid's Email Activity feed, which is a paid add-on. Accounts without the additional email activity history add-on return a permission error for this table even with `email_activity.read` granted. The table is unselected by default for that reason. The add-on stores 30 days of history, so the first sync backfills at most the last 30 days. ## 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_at` timestamp). 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](https://posthog.com/docs/data-pipelines/sources.md#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. `stats` filters on its daily aggregate date, and backfills the last year on the first sync. `message_activity` narrows a time window over the Email Activity feed. The remaining tables — including `unsubscribe_groups`, `unsubscribe_group_suppressions`, `marketing_lists`, `singlesends`, `singlesend_stats`, `singlesend_link_stats`, and `templates` — 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 | — | — | | `unsubscribe_group_suppressions` | Membership rows of unsubscribe groups: one row per email address suppressed from a group. | Full refresh | — | — | | `marketing_lists` | Marketing contact lists used to segment recipients for campaigns. | Full refresh | — | — | | `singlesends` | Single Sends: one-time Marketing Campaigns emails sent to lists or segments. | Full refresh | — | — | | `singlesend_stats` | Lifetime engagement statistics for each Single Send, such as requests, deliveries, opens, and clicks. | Full refresh | — | — | | `singlesend_link_stats` | Click counts for each link in each Single Send. | 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: 1. In SendGrid, go to **Settings > API Keys** and edit the existing key. 2. Add read access for the table's area, using the [required scopes](#required-scopes) table above. 3. 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 the marketing tables (`marketing_lists`, `singlesends`, `singlesend_stats`, or `singlesend_link_stats`) or `message_activity` fails while the other tables sync, check your account's add-ons before changing scopes. The marketing tables need Marketing Campaigns, and `message_activity` needs the additional email activity history add-on. Without them, no scope grant will make those tables 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](https://posthog.com/docs/data-warehouse/troubleshooting.md). If that doesn't help, [contact support](https://us.posthog.com/#panel=support%3Asupport%3Adata_warehouse%3A%3Atrue) – we're happy to help.