Guide
Migrate from SendGrid
Move from SendGrid to Emailit. Map concepts, API fields, SMTP settings and Event Webhook names, then bring over suppressions, dynamic templates and DNS.
This guide maps SendGrid concepts, API calls, webhooks, suppressions and templates to their Emailit equivalents. Read Migrate to Emailit first for the overall order and how to run both providers in parallel.
Concepts
| SendGrid | Emailit |
|---|---|
| Account and subusers | Account and workspaces. Each workspace has its own domains, keys, members and credits. |
| API key with permissions | API key: Full Access, or Sending Only optionally restricted to one domain |
| Domain authentication | Sending domain with SPF, DKIM and return path records |
| Link branding | Tracking subdomain, a CNAME such as go.acme.com |
| Single sender verification | Not available. Every From address must be on a verified domain. |
| Dynamic templates | Templates with an alias and versions, rendered with Temple |
| Event Webhook | Webhooks |
| Inbound Parse | Inbound email |
| Suppressions | Suppressions |
| Unsubscribe groups | Not available. Use audiences and campaign unsubscribe links. |
| Marketing contacts and lists | Contacts and audiences |
| Single Sends | Campaigns |
| Email Activity | Email APIEmails and Email APILogs |
| Categories and custom args | meta |
| Dedicated IPs and IP pools | Dedicated IPs on request |
| Email address validation | Email verification |
Update your API calls
SendGrid’s POST /v3/mail/send becomes POST /v2/emails. The request is flatter: there are no personalizations, and addresses are plain strings.
curl https://api.sendgrid.com/v3/mail/send \
-H "Authorization: Bearer $SENDGRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"personalizations": [{ "to": [{ "email": "ada@example.com" }] }],
"from": { "email": "hello@acme.com", "name": "Acme" },
"subject": "Your receipt",
"content": [
{ "type": "text/plain", "value": "Thanks for your order." },
{ "type": "text/html", "value": "<p>Thanks for your order.</p>" }
]
}'curl https://api.emailit.com/v2/emails \
-H "Authorization: Bearer $EMAILIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <hello@acme.com>",
"to": "ada@example.com",
"subject": "Your receipt",
"text": "Thanks for your order.",
"html": "<p>Thanks for your order.</p>"
}'| SendGrid | Emailit |
|---|---|
Authorization: Bearer SG.… |
Authorization: Bearer secret_… |
from: { email, name } |
from: "Name <email>" |
personalizations[].to[] |
to, a string or an array of up to 50 addresses |
personalizations[].cc[], bcc[] |
cc, bcc |
reply_to: { email } |
reply_to |
subject |
subject |
content[] with text/plain and text/html |
text and html |
template_id |
template, a template ID or alias |
personalizations[].dynamic_template_data |
variables |
attachments[] with content, filename, type, content_id |
attachments[] with content, filename, content_type, content_id, or a url instead of content |
headers |
headers |
custom_args, categories |
meta, an object of string values returned in webhook events |
send_at (Unix time) |
scheduled_at, which accepts the same Unix time, ISO 8601 or plain English |
tracking_settings.open_tracking and click_tracking |
tracking: { "loads": true, "clicks": true } |
asm (unsubscribe groups) |
Not available |
202 Accepted with an X-Message-Id header |
200 with a JSON body: id, status: "accepted", and ids with one ID per recipient |
Each recipient in an Emailit request becomes its own email with its own ID. To send different variables to different people, which SendGrid does with several personalizations, send one request per recipient. Add an Idempotency-Key header so retries are safe. See Send an email.
Switch SMTP settings
| Setting | SendGrid | Emailit |
|---|---|---|
| Host | smtp.sendgrid.net |
smtp.emailit.com |
| Port | 587, 465, 2525 or 25 |
587 (STARTTLS), 465 (TLS), 2525, 2587 or 25 |
| Username | apikey |
emailit |
| Password | Your SendGrid API key | Your Emailit API key |
Emailit doesn’t read the X-SMTPAPI header. Remove it, and set tracking on the domain instead. See SMTP settings.
Map webhook events
| SendGrid event | Emailit event |
|---|---|
processed |
email.accepted (API only) |
deferred |
email.attempted |
delivered |
email.delivered |
bounce |
email.bounced |
dropped |
email.suppressed when the recipient is on the suppression list |
open |
email.loaded |
click |
email.clicked |
spamreport |
email.complained |
unsubscribe, group_unsubscribe |
email.unsubscribed, for campaign email only |
| Inbound Parse POST | email.received, then fetch the content with GET /emails/{id} |
Like SendGrid, Emailit posts a JSON array of events. The fields differ:
- The event name is in
type, and the email is indata.object. Usedata.object.id(theem_ID from the send response) instead ofsg_message_id, anddata.object.toinstead ofemail. - Your
metavalues come back indata.object.meta. - Emailit signs requests with HMAC-SHA256 instead of SendGrid’s ECDSA public key. Verify
X-Emailit-Signaturewith yourwhsec_secret. See Request signature.
for (const event of req.body) {
const email = event.data.object;
if (event.type === 'email.bounced') markBounced(email.to, email.id);
if (event.type === 'email.complained') unsubscribe(email.to);
}Move suppressions
-
In SendGrid, export your Bounces, Spam Reports, Invalid Emails and Global Unsubscribes, from the suppression pages or with the
/v3/suppression/*API endpoints. Blocks are usually temporary, so you can leave them out. -
Build one CSV with the columns
email,type,reason:email,type,reason old-address@example.com,recipient,sendgrid bounce angry@example.com,recipient,sendgrid spam reportUse the type
recipientfor addresses that must never receive email. It blocks API, SMTP and campaign sends. The typesbounce,complaintandunsubscribeonly stop campaigns. -
In Email APISuppressions, select Import and upload the file. Each file can have up to 10,000 rows and can be at most 8 MB, so split larger lists. Duplicates are skipped.
For group unsubscribes from marketing email, import those people as contacts with unsubscribed set, rather than suppressing them from all email. See Manage suppressions.
Move templates
Export each dynamic template’s HTML from SendGrid, then import it in Email MarketingTemplates or create it with the Templates API. Give each template an alias, such as receipt, and send it with "template": "receipt".
Both use double curly braces, but Temple is smaller than Handlebars:
| SendGrid (Handlebars) | Emailit (Temple) |
|---|---|
{{first_name}} |
{{first_name}} |
{{{html_block}}} |
{{html_block}}. Temple never escapes HTML, so escape user input yourself. |
{{insert name "default=there"}} |
{{name|"there"}} |
{{#if plan}}…{{else}}…{{/if}} |
The same |
{{#each items}}…{{/each}} |
Not supported. Render the list in your code and pass it as one variable. |
{{#equals plan "pro"}}…{{/equals}} |
Not supported. Pass a boolean such as is_pro and use {{#if is_pro}}. |
See Temple and Import and export templates.
Change DNS
Add your domain in Email APIDomains and publish the Emailit records. They use their own names (emailit._domainkey, emailit.<domain>, and optionally go and inbound), so they don’t conflict with SendGrid’s domain authentication or link branding CNAMEs. Keep your DMARC record. After the cutover, remove the SendGrid CNAMEs. See DNS records.
If you used Inbound Parse, point the MX record of your parse hostname at Emailit instead. To keep the same hostname, such as parse.acme.com, set the domain’s inbound_key to parse with the API. See Set up inbound.
Next steps
- Go-live checklist
- Set up webhooks
- Priority migration: let Emailit engineers do the move with you