What changes, what doesn't
Both are REST APIs built around one call per email plus webhooks for delivery
events, so the shape of your integration survives. SendGrid's structure is more
elaborate (personalizations arrays, content type/value pairs) because it
grew to support bulk personalization at scale; EuroMail's request body is a
flatter JSON object for the common case. The honest side-by-side, including
where SendGrid remains the better fit, is at
euromail vs SendGrid.
Verify your domain on EuroMail while SendGrid still handles production traffic, then cut over. A config change, not a rewrite.
API mapping
| SendGrid | EuroMail | |
|---|---|---|
| Endpoint | POST https://api.sendgrid.com/v3/mail/send | POST https://api.euromail.dev/v1/emails |
| Auth header | Authorization: Bearer SG.xxx | Authorization: Bearer em_live_...: same header, only the key changes (X-EuroMail-Api-Key also works) |
| Recipients | personalizations[0].to (array of {email, name}) | to. String or array |
| Sender | from: { email, name } | from: "Name <email>" or bare address |
| Body | content: [{ type: "text/html", value: "..." }] | html_body, text_body. Flat fields |
| Attachments | attachments: [{ content, filename, type }] (base64) | attachments: [{ filename, content, content_type }] (base64). Same shape, content_type required |
| Scheduling | send_at (Unix timestamp, max 72 h ahead) | send_at (RFC 3339), no advance-window limit |
| Grouping | categories (max 10) | tags (array of strings) + free-form metadata object |
| Idempotency | None | idempotency_key body field. Same key always returns the original result |
| Success response | 202 Accepted, empty body | 202 Accepted with { "id", "status": "queued", ... } |
The biggest structural change: SendGrid nests recipients inside
personalizations to support per-recipient template substitution in one
request. EuroMail's to is a flat field, for personalized batch sends, loop
and call the endpoint once per recipient (or per intended batch), passing
idempotency_key so retries never double-send. See
Sending Emails.
Code before and after
curl:
# Before: SendGrid
curl -X POST https://api.sendgrid.com/v3/mail/send \
-H "Authorization: Bearer SG.xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"personalizations": [{ "to": [{ "email": "[email protected]" }] }],
"from": { "email": "[email protected]" },
"subject": "Welcome aboard",
"content": [{ "type": "text/html", "value": "<h1>Welcome!</h1>" }]
}'
# After: EuroMail
curl -X POST https://api.euromail.dev/v1/emails \
-H "Authorization: Bearer em_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": "[email protected]",
"subject": "Welcome aboard",
"html_body": "<h1>Welcome!</h1>"
}'
TypeScript:
// Before: SendGrid
import sgMail from "@sendgrid/mail";
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
await sgMail.send({
to: "[email protected]",
from: "[email protected]",
subject: "Welcome aboard",
html: "<h1>Welcome!</h1>",
});
// After: EuroMail
import { EuroMail } from "@euromail/sdk";
const client = new EuroMail({ apiKey: process.env.EUROMAIL_API_KEY! });
await client.sendEmail({
from: "[email protected]",
to: "[email protected]",
subject: "Welcome aboard",
html_body: "<h1>Welcome!</h1>",
});
Python, Rust, and Go SDKs use the same field names as the REST API.
SMTP cutover
| Setting | SendGrid | EuroMail |
|---|---|---|
| Host | smtp.sendgrid.net | smtp.euromail.dev |
| Port | 587 | 587 (STARTTLS) or 465 (implicit TLS) |
| Username | apikey (literal) | apikey (literal). Same convention |
| Password | your SendGrid API key | your EuroMail API key (em_live_...) |
Same literal username, so most SMTP-relay plugin presets need only a host and password change. Full walkthroughs (WordPress, framework mailers) in Send Email via SMTP.
DNS: verify your domain before switching
- Add your domain in the EuroMail dashboard and publish its DKIM, SPF, and DMARC records. See Domain Verification.
- Keep SendGrid's records in place during the dual-run. DKIM lives under a
different selector and SPF
include:s stack, so both providers verify simultaneously. - Remove SendGrid's records on your own schedule after cutover.
Webhook event mapping
| SendGrid event | EuroMail event |
|---|---|
delivered | sent |
bounce | bounced |
blocked, deferred | deferred |
spamreport | complained |
open | opened |
click | clicked |
processed, dropped | No equivalent. The 202 API response already confirms acceptance or immediate rejection |
unsubscribe, group_unsubscribe | Handled via suppression lists, not a webhook event |
account_status_change | N/A. See your account status in the dashboard instead |
Signature verification changes: SendGrid signs with ECDSA over
X-Twilio-Email-Event-Webhook-Signature / -Timestamp headers, which needs
their helper library to verify correctly. EuroMail signs every payload with
plain HMAC-SHA256 in the X-Euromail-Signature header. A five-line function
in any language, no library required. Swap your handler for the one in
Webhooks.
Bring your suppression list
Export suppressions (bounces, spam reports, unsubscribes) from the SendGrid dashboard, then import them in bulk:
curl -X POST https://api.euromail.dev/v1/suppressions/import \
-H "Authorization: Bearer em_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"emails": ["[email protected]", "[email protected]"],
"reason": "migrated from SendGrid: suppressed"
}'
One request takes up to 10,000 addresses, so split a bigger export into
chunks. The response reports how many were added and lists any address it
could not parse. The key needs the contacts:manage scope.
Details in Suppression Lists.
Your first 24 hours
Every new account spends its first 24 hours -- counted from account creation, not from your first send -- inside a probation window with two limits designed to stop day-one spam blasts without starving a real migration:
- A send-rate cap: 5 emails per minute and 25 per hour on day one, loosening over the first week.
- A recipient allowance: a token bucket of 20 that refills at 2 per
hour, counted per deliverable recipient (
toplus every cc and bcc). A settled, non-zero invoice raises the allowance to 1,000 -- a paid plan still inside its trial does not count as a payment. The allowance lifts entirely once the account is 24 hours old.
A refused send returns 429 with a retry-after header (the SMTP relay
returns 452, which well-behaved mailers retry). Test sends from a
not-yet-verified domain to your own address (sandbox mode) never consume
the allowance, so integration testing is free.
The practical consequence for a migration: create your EuroMail account and verify your domain at least a day before cutover, so the recipient allowance has expired by the time production traffic moves. If you must cut over with real volume on day one, settle your first invoice first (1,000 recipients covers a typical day) or keep SendGrid running in parallel.
After that, the account's age and payments set the pace:
| When | Send rate | Monthly volume |
|---|---|---|
| First 24 hours | 5/minute, 25/hour, plus the recipient allowance above | 3,000 |
| Days 2 to 7 | 15/minute, 75/hour | 3,000 until your first invoice is paid |
| From day 8 | 100/minute, 500/hour | Your plan's volume once an invoice has been paid |
A paid plan that is still in its trial counts as unpaid: until a non-zero
invoice has been paid, the account is held to the free allowance of 3,000
emails a month whatever plan it is on. Sends over a limit return 429.
Plan the cutover around these numbers: if your normal hourly volume is above
the rate for the week you cut over in, keep the old provider carrying part
of the traffic until it fits.
Honest gaps
- Dynamic templates with Handlebars-style logic don't port directly.
SendGrid's
template_id+dynamic_template_datasystem supports conditionals and loops in templates; move that logic into your application code, or rebuild the template with EuroMail's template engine. - No
categoriescap. EuroMail'stagsarray has no fixed limit, unlike SendGrid's 10-category ceiling. Nothing to adapt, just noting the difference. - IP pools and warm-up. If you use SendGrid's dedicated IP pools, EuroMail handles IP reputation and warm-up automatically per account. See Deliverability Insights.
- Full comparison at euromail vs SendGrid.
Cutover checklist
- Create a EuroMail account at least 24 hours before cutover (probation limits expire with account age) and an API key with the
emails:sendandcontacts:managescopes (API Keys). - Verify your sending domain; confirm DKIM/SPF/DMARC show green.
- Register your webhook endpoint and swap the signature verification.
- Import suppressions from your SendGrid export with
POST /v1/suppressions/import. - Switch the endpoint, auth header, and flatten
personalizations/contentintoto/html_body/text_body(or the two SMTP settings), accepting202as success. - Check your volume against the limits for the week you cut over in (see
above), and pay the first invoice if you need more than 3,000 emails this
month. For bulk jobs, send up to 500 messages per request with
POST /v1/emails/batchand back off on429(wait forretry-afterwhen the response has one). - Send a staging email end-to-end; confirm the
sentwebhook arrives. - Cut production traffic over; watch the first hour in Analytics and Deliverability Insights.
- Keep SendGrid read-only for a rollback window, then retire it.