---
title: SendGrid Compatibility
path: tutorials/sendgrid-compatibility
status: published
---

# SendGrid Compatibility

ScaiSend's `/v3/` surface is a SendGrid v3 API clone. If you have existing code written against `api.sendgrid.com/v3/mail/send`, you can point it at `https://scaisend.scailabs.ai/v3/mail/send` and most of it will work without changes. This page covers what's compatible, what's not, and how to migrate.

## What's compatible

The core sending surface and the features that matter for most integrations:

| SendGrid feature | ScaiSend support | Notes |
|------------------|------------------|-------|
| `POST /v3/mail/send` (full schema) | Yes | Matches request body exactly |
| Personalizations (up to 1000) | Yes | Same structure, same limits |
| Dynamic templates (`template_id: d-...`) | Yes | Handlebars syntax |
| Attachments (base64, up to 10) | Yes | Same limits |
| Inline attachments (`disposition: inline`, `content_id`) | Yes | Same CID model |
| `send_at` (scheduled delivery) | Yes | Requires `mail.schedule` scope |
| `batch_id` / batch status | Yes | Includes `POST /v3/mail/batch`, `GET /v3/mail/batch/{id}` |
| `asm` (Advanced Suppression Manager) | Yes | Groups and per-group opt-outs |
| `mail_settings.sandbox_mode` | Yes | Plus test-key-level sandbox enforcement |
| `mail_settings.bypass_*` | Yes | All four flags |
| `tracking_settings.click_tracking` | Yes | Including `enable_text` |
| `tracking_settings.open_tracking` | Yes | Including `substitution_tag` |
| `tracking_settings.subscription_tracking` | Yes | Including `substitution_tag` and custom HTML/text |
| `tracking_settings.ganalytics` | Yes | UTM params |
| `categories` | Yes | Max 10 per message |
| `custom_args` | Yes | Preserved on message and events |
| `headers` | Yes | Custom headers passed through |
| Templates — `/v3/templates` + versions | Yes | Legacy and dynamic generations |
| Messages — `/v3/messages` | Yes | Listing and detail |
| API keys — `/v3/api_keys` | Yes | Scopes match |
| Stats — `/v3/stats`, `/v3/stats/categories` | Yes | Daily aggregates |
| Bounces — `/v3/suppression/bounces` | Yes | List, get, delete, bulk import |
| Spam reports — `/v3/suppression/spam_reports` | Yes | List, get, delete |
| Global unsubscribes — `/v3/asm/suppressions/global` | Yes | List, check, add, delete |
| Suppression groups — `/v3/asm/groups` | Yes | CRUD + group suppressions |
| Webhook endpoints — `/v3/user/webhooks` | Yes | Multiple URLs |
| Event Webhook settings — `/v3/user/webhooks/event/settings` | Yes | SendGrid's single-URL model |
| Signed webhook requests | Yes | HMAC-SHA256; see note below on headers |

## What's different

A few places where ScaiSend diverges from SendGrid. Most are additive (ScaiSend has more) or under-the-hood (different but invisible).

### API key prefixes

SendGrid keys start with `SG.`. ScaiSend keys start with `sg_live_` or `sg_test_`:

```
SendGrid:  SG.abc123.xyz789
ScaiSend:  sg_live_a1b2c3d4e5f6...   (production)
           sg_test_a1b2c3d4e5f6...   (sandbox)
```

Test keys in ScaiSend force sandbox mode — you can't accidentally send real mail with a test key. SendGrid doesn't have this distinction.

### Webhook signature headers

SendGrid signs webhooks with `X-Twilio-Email-Event-Webhook-Signature` and `X-Twilio-Email-Event-Webhook-Timestamp`, using ECDSA with a base64 PEM public key.

ScaiSend signs with `X-ScaiSend-Signature` and `X-ScaiSend-Timestamp`, using HMAC-SHA256 with a per-endpoint signing secret. Simpler, symmetric, and the secret rotates with a single API call.

The event payload format is the same (same event names, same structure); only the transport-level signing is different.

### Sender domain verification

SendGrid has "Domain Authentication" — create a domain in the UI, it gives you CNAMEs. ScaiSend uses TXT records directly for DKIM, SPF, and DMARC. Simpler DNS (no CNAME dance; fewer records), but you can't do the CNAME-indirect-proxy trick that SendGrid documents as its default.

See [Sender Domains](../concepts/sender-domains) for the ScaiSend flow.

### Multi-tenant model

SendGrid has "Subusers" — sub-accounts nested under a main account with their own credentials. ScaiSend has partner/tenant/user, managed via [ScaiKey](../concepts/architecture#scaikey-integration). If you were using Subusers to serve multiple customers, map each customer to a ScaiSend tenant and don't use Subusers.

### What's not implemented

Intentionally omitted from ScaiSend:

- **Marketing Campaigns** (`/v3/marketing/campaigns`). ScaiSend is transactional-first. If you need campaign tooling, run a separate marketing product that calls `/v3/mail/send` per recipient.
- **Contacts / lists** (`/v3/marketing/contacts`). No first-class contact management; ScaiSend tracks suppressions but not an address book.
- **Email validation API** (`/v3/validations/email`). No upfront email-address validator; use a third-party tool before sending.
- **IP access management UI** (admin IP allowlists). Not implemented.
- **Inbound Parse Webhook.** ScaiSend processes DSN bounces and FBL reports on the inbound side; it does not accept arbitrary inbound mail for parsing into webhooks. Use a separate inbound-mail tool if that's a requirement.

If a SendGrid feature you depend on isn't listed above, check the [API Reference](../06-reference/) — the list of implemented endpoints is authoritative.

## Migration steps

### 1. Provision ScaiSend

- Create your partner and tenant in ScaiKey (or run `scaisend sync --full` if you're bringing an existing ScaiKey).
- Add your sender domain (`POST /api/admin/domains`) and publish the DKIM/SPF/DMARC records.
- Verify the domain (`POST /api/admin/domains/{id}/verify`).

### 2. Create an equivalent API key

```bash
curl -X POST https://scaisend.scailabs.ai/v3/api_keys \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name": "migrated-from-sendgrid", "environment": "live", "scopes": ["mail.send"]}'
```

Store the returned key.

### 3. Import your suppression lists

SendGrid export → ScaiSend import.

```bash
# Download your SendGrid bounces/spam/unsubscribes as CSV
# Then:
curl -X POST https://scaisend.scailabs.ai/v3/suppression/bounces/import \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @sendgrid-bounces.csv

curl -X POST https://scaisend.scailabs.ai/v3/suppression/spam_reports/import \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @sendgrid-spam-reports.csv
```

For unsubscribes, call `POST /v3/asm/suppressions/global` (or `POST /v3/asm/groups/{id}/suppressions` if you're moving group-scoped unsubscribes).

### 4. Mirror your templates

SendGrid dynamic templates and ScaiSend dynamic templates use the same Handlebars syntax. Create the template, create a version, copy the HTML/subject/preheader verbatim. The IDs will be different (ScaiSend generates its own `d-*` IDs), so update wherever your code references them.

```bash
# Create
curl -X POST https://scaisend.scailabs.ai/v3/templates \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "welcome-email", "generation": "dynamic"}'

# Create version (copy html_content from SendGrid verbatim)
curl -X POST https://scaisend.scailabs.ai/v3/templates/d-new-id/versions \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "v1", "subject": "...", "html_content": "...", "active": 1}'
```

### 5. Re-subscribe webhooks

Register a new webhook endpoint on ScaiSend, save the signing secret, update your webhook handler:

```bash
curl -X POST https://scaisend.scailabs.ai/v3/user/webhooks \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/webhooks/scaisend",
    "enabled_events": ["processed", "delivered", "bounce", "spam_report", "unsubscribe", "open", "click"]
  }'
```

Your webhook handler needs to switch from SendGrid's ECDSA signature verification to ScaiSend's HMAC-SHA256. See [Webhooks](webhooks) for the recipe.

### 6. Flip the base URL

The change is usually a single constant:

```diff
- const BASE_URL = "https://api.sendgrid.com";
+ const BASE_URL = "https://scaisend.scailabs.ai";
```

Deploy. Watch your message stream for the first few sends. Once you're confident, retire the SendGrid integration.

## Sending from the SendGrid SDK

The official SendGrid SDKs let you set a custom base URL:

```python
# sendgrid-python
import sendgrid
from sendgrid.helpers.mail import Mail, Email, To, Content

sg = sendgrid.SendGridAPIClient(api_key="sg_live_...")
sg.client.host = "https://scaisend.scailabs.ai"  # override

mail = Mail(
    from_email=Email("hello@mail.example.com"),
    to_emails=To("ada@example.com"),
    subject="Hello from the SDK",
    plain_text_content=Content("text/plain", "It works"),
)
resp = sg.send(mail)
print(resp.status_code)  # 202
```

```typescript
// @sendgrid/mail
import sgMail from "@sendgrid/mail";

sgMail.setApiKey(process.env.SCAISEND_API_KEY!);
sgMail.setSubstitutionWrappers("{{", "}}");
sgMail.setClient({
  // @sendgrid/client exposes this; check your SDK version
  // or just use the raw fetch pattern from "Sending Mail"
});

await sgMail.send({
  to: "ada@example.com",
  from: "hello@mail.example.com",
  subject: "Hello from the SDK",
  text: "It works",
});
```

The Python SDK is cleaner on this. For TypeScript, a plain `fetch` often ends up simpler than configuring the official SDK to point at ScaiSend.

## What doesn't work

If you see these, don't try to work around them — they're intentional differences:

- **SendGrid's SDK "sandbox validation" tools.** The SDK has local validators; ScaiSend has its own validator on the server side. It catches different things; trust the 400 response over the SDK's local check.
- **Subuser impersonation.** Don't try; use tenants.
- **Marketing campaign endpoints.** They don't exist.
- **API key with `SG.` prefix.** Won't validate. Get a ScaiSend key.

## What's next

- [Sending Mail](sending-mail) — the full `/v3/mail/send` reference.
- [Authentication](../concepts/authentication) — creating and using keys.
- [Suppressions](suppressions) — CSV bulk import endpoints for migration.
