Migrate from SendGrid
Of all the providers, SendGrid is the closest to a literal find-and-replace — the send call already uses from, to, subject, and html. What changes is where your mail lives: eusend sends and stores everything on EU infrastructure, operated by a Norwegian company, instead of a US provider.
The code change
Install the SDK with npm install @eusend_dev/sdk. The field names match; the differences are the client being an instance rather than a global singleton, and eusend returning { data, error } instead of throwing on failure.
import sgMail from '@sendgrid/mail'
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
await sgMail.send({
from: 'Acme <[email protected]>',
to: '[email protected]',
subject: 'Welcome',
html: '<p>Thanks for signing up.</p>',
})import { Eusend } from '@eusend_dev/sdk'
const eusend = new Eusend(process.env.EUSEND_API_KEY)
await eusend.emails.send({
from: 'Acme <[email protected]>',
to: '[email protected]',
subject: 'Welcome',
html: '<p>Thanks for signing up.</p>',
})Field mapping
| SendGrid | eusend | Notes |
|---|---|---|
| from / to / cc / bcc / subject / html / text | Same names | from must use a domain you verified in eusend |
| replyTo | replyTo | String or array |
| personalizations | batch.send([...]) | One message object per recipient, up to 100 per request |
| trackingSettings | trackOpens / trackClicks | Two booleans instead of a nested settings object; both default to true |
| templateId + dynamicTemplateData | templateId | eusend has server-side templates; see the docs for the variable syntax |
| categories / customArgs | — | No direct equivalent yet. Custom headers are the usual workaround. |
If you send over SMTP
SendGrid's SMTP setup — a literal username and your API key as the password — carries straight over. Swap the host, change the username from apikey to eusend, and make sure you're on port 465 with implicit TLS: eusend does not support STARTTLS on 587, which is SendGrid's usual default.
// Before: smtp.sendgrid.net, user "apikey", API key as password
// After: same idea, different literal username — and use implicit TLS on 465
const transport = nodemailer.createTransport({
host: 'smtp.eusend.dev',
port: 465,
secure: true,
auth: { user: 'eusend', pass: process.env.EUSEND_API_KEY },
})Webhooks
SendGrid's Event Webhook posts arrays of event objects; eusend posts one signed event per delivery attempt. The types map cleanly:
| SendGrid event | eusend event |
|---|---|
| processed | email.sent |
| delivered | email.delivered |
| bounce, dropped | email.bounced |
| spamreport | email.complained |
| open | email.opened |
| click | email.clicked |
Signature verification changes from SendGrid's ECDSA scheme to HMAC-SHA256 — simpler to implement, same guarantee. The webhook docs show the exact scheme, and you can subscribe your endpoint to only the events you handle.
What's honestly different
- Attachments carry over. SendGrid's
attachmentsarray maps almost as-is — keepcontent(base64) andfilename, renametype→content_typeandcontent_idstays the same for inline images (dropdisposition). Up to 20 files, 10 MB combined per message. - Bring your suppression list. eusend suppresses hard bounces and complaints automatically going forward, but it can't see your SendGrid history. Export your suppressions and import the CSV before your first send, so addresses that already bounced don't get a fresh attempt from a new IP.
- Marketing campaigns wait. Transactional sending is open today; bulk campaigns open as the platform warms. If you use SendGrid Marketing Campaigns, plan that part for later.
Before you flip production
- Verify your sending domain and add its DKIM and DMARC records — the Domains page generates them. Sends from unverified domains are rejected.
- Migrate against an
eu_test_API key first — test sends are accepted and tracked but never delivered — then switch toeu_live_. - Keep SendGrid alive for a week or two after cutover so in-flight events and your suppression export have somewhere to come from.