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 an EU 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 <hello@acme.com>',
to: 'user@example.com',
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 <hello@acme.com>',
to: 'user@example.com',
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 keep those addresses out of your sends.
- 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, SPF, 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.