Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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_:

scdoc
1
2
3
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 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. 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 — 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
1
2
3
4
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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
1
2
3
4
5
6
7
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 for the recipe.

6. Flip the base URL#

The change is usually a single constant:

diff
1
2
- 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// @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#

Updated 2026-05-17 01:33:27 View source (.md) rev 1