Moving from SendGrid

Move an app built for SendGrid to Mailroomly without rewriting it: it keeps its SendGrid library, its event handler and its Inbound Parse handler, and you change the API host and the key.

What stays the same

Mailroomly speaks SendGrid's Mail Send API and its webhook formats:

  • The endpoint. POST /v3/mail/send, at the root of https://app.mailroomly.com, takes SendGrid's request: personalizations (with to, cc, bcc, subject, substitutions, headers and custom_args), from, reply_to or reply_to_list, subject, content, attachments, headers, categories, custom_args, asm, open and click tracking, and sandbox mode. It answers 202 with an X-Message-Id, and errors in SendGrid's {"errors": [...]} shape.
  • The key. An app's API key goes in Authorization: Bearer, where the SendGrid key was.
  • Marketing email. A request with asm sends marketing email: the recipient can unsubscribe, and <%asm_group_unsubscribe_raw_url%> becomes each recipient's own unsubscribe link. Without it, the email is transactional.
  • Sandbox mode. mail_settings.sandbox_mode checks the request (its fields, addresses, sender domain and key) and sends nothing.
  • The Event Webhook. With the SendGrid-compatible format, events arrive as SendGrid's: processed, dropped, delivered, deferred, bounce, open, click, spamreport and unsubscribe, with your custom_args and category, and sg_message_id starting with the X-Message-Id. They are signed as SendGrid signs them, so your handler's signature check keeps working with the app's verification key.
  • Inbound Parse. Email your addresses receive arrives as SendGrid's Inbound Parse form, with its fields and attachments.

Every field and event: SendGrid compatibility.

Before you start

  • A verified domain. The app's sending domain must be verified in Mailroomly (Your own domain). Mailroomly's DNS records and SendGrid's sit side by side, so both can send for the same domain while you move.
  • A daily limit that fits. A new account sends to a limited number of recipients per day until it becomes a trusted account. Check Plan before moving a busy app, and ask for a higher limit if you need one (Sending trust).
  • Your blocked addresses. SendGrid's suppressions (bounces, spam reports, unsubscribes, invalid addresses) don't move by themselves. Export them from SendGrid and block them in Activity › Blocked › Block an address, or through the API, before the first send (Blocked). Block bounces, spam reports and invalid addresses for every type of email, and unsubscribes for marketing.
  • What Mailroomly doesn't do. Dynamic templates by template_id, scheduled sends (send_at, batch_id), IP pools, legacy sections, footers and subscription tracking are refused with the reason, never ignored: render the content in the app, and send when the email should go.

Move an app over

Apps move one at a time. Apps › Move over from SendGrid (also in Ctrl K) walks you through the three steps in the web app: connect an app in SendGrid's format, point SendGrid's library at Mailroomly, and move your webhooks.

1. Create the app and its key

In Move over from SendGrid, choose Connect an app under Connect an app in SendGrid’s format. Give the App name and choose What it does (Sends email is enough for most apps), then Which emails does this key reach?, then Create the app and its key. The app's webhooks already use the SendGrid-compatible format.

The key, starting with mp_live_, takes the place of your SendGrid API key. It is shown only once: copy it into the app's secret store. To narrow it to the app's domains or to one type of email, create another key on the app's page and revoke the first (Apps and keys).

2. Point SendGrid's library at Mailroomly

Change two values in the app's configuration: the API host and the key. The code that builds and sends the email stays the same.

// Node
const client = require('@sendgrid/client')
const sgMail = require('@sendgrid/mail')

client.setApiKey(process.env.MAILROOMLY_API_KEY)
client.setDefaultRequest('baseUrl', 'https://app.mailroomly.com')
sgMail.setClient(client)
# Python
sg = SendGridAPIClient(api_key=os.environ["MAILROOMLY_API_KEY"], host="https://app.mailroomly.com")

The other libraries (PHP, Ruby, Go, Java and C#) have the same setting: SendGrid's libraries. Without a library, send the same request to https://app.mailroomly.com/v3/mail/send.

Check how the app sends marketing: email sent with asm is marketing, and email without it is transactional. A key restricted to marketing sends marketing whatever the request says.

3. Move your webhooks

On the app's page, open the Webhook step:

  1. Webhook URL: the endpoint that received SendGrid's Event Webhook.
  2. Payload format: SendGrid-compatible (already set by the guide).
  3. Events to send: tick what the endpoint handles: Accepted for sending, Not sent (invalid or blocked address), Delivered, Bounced, Delivery delayed, Opened, Clicked, Complaint, Unsubscribed and Inbound message.
  4. Save changes, then Send a test event to see what your endpoint answers.
  5. Under Advanced details, copy the Verification key (SendGrid’s signed Event Webhook) into your event handler, where SendGrid's verification key was.

Rotate secret renews the verification key too, at once: your handler can't verify events until it has the new key, so put it there right after rotating. Deliveries it refuses meanwhile are retried.

One URL receives every event the app chooses, and the email your addresses receive. Received email comes as a form (multipart/form-data), events as a JSON list, so one handler can tell them apart. If your event handler and your Inbound Parse handler sit at two URLs, give the receiving address its own Inbound webhook URL (step 4) and untick Inbound message here, so received email reaches only that URL. The URL can't carry a user name and password: check the signature instead (Webhooks).

4. Receive email, if the app did with SendGrid

For an app that received email through SendGrid's Inbound Parse:

  1. Set the domain to Sends and receives (What this domain does, in its Settings), usually the same subdomain the app used with Inbound Parse, never a domain whose mail lives elsewhere (Addresses and domains).
  2. Create the receiving address and choose the app as its Owning app. Give it an Inbound webhook URL if your Inbound Parse handler has its own URL. Requests arrive in Inbound Parse's form, signed with the app's secret in X-Messaging-Signature.
  3. If Inbound Parse received for any address of the subdomain, make the address the domain's Catch-all address: the address each email was sent to is in its envelope field.
  4. Change the domain's MX record to the value on the domain's page (DNS records), and wait until the page shows it in place. Lower the record's time to live a day ahead, so you can switch back quickly.

Setting an address's owning app or inbound webhook URL needs a person signed in to Mailroomly: an agent can't do it (AI agents).

5. Test

Send one email from the app with mail_settings.sandbox_mode on: the answer is 200 and nothing is sent. Then send one for real: the answer is 202 with an X-Message-Id, and the email shows in Activity, going from Sent to Delivered. Send a test as this app, on the app's page, sends one to you and lists the events your webhook received (Send a test email). For receiving, send an email to the address: it appears in the inbox, and Webhook deliveries shows the delivery to your endpoint.

6. Watch Activity

For the first days, keep an eye on:

  • Activity: every send, with its route and events (A send's page). Blocked shows the addresses Mailroomly doesn't send to, and why.
  • Webhook deliveries: deliveries your endpoint did not accept, to fix and replay.
  • Notices: bounces, complaints, daily limits and failed webhooks, each saying what to do.

Switching back

Keep the SendGrid account, its DNS records and its webhooks until the move is done. To go back, restore the two values, the API host and the key: the app sends through SendGrid again within minutes. For receiving, point the MX record back to SendGrid. Email already accepted by Mailroomly is still sent, and stays in Activity.

What differs

In Mailroomly
Templates Dynamic templates by template_id are refused: render the content in the app before sending.
SendGrid's other APIs Marketing campaigns and contacts, suppressions and unsubscribe groups, statistics, the activity feed, subusers, sender verification and domain authentication are not emulated. Use the REST API and the web app (Activity, Blocked, Addresses, Campaigns).
Senders Any address on a verified domain sends; there are no single senders to verify.
Each personalization Is one email in Mailroomly, with its own page in Activity. They share the call's X-Message-Id.
Unsubscribe groups An unsubscribe covers all of your organization's marketing, not one group.
Ids The X-Message-Id and the ids in sg_message_id are UUIDs.
Blocked recipients Left out of the email, as SendGrid drops them, and reported as a dropped event.
Limits 500 personalizations per request, 50 recipients and 10 MB per email, 100 requests per second per key. An attachment every personalization shares counts once for each: the personalizations of a request come to at most 50 MB.
Refusals A field Mailroomly doesn't support is 400 with the field's name; an unverified domain, a key outside its scope or marketing that isn't allowed is 403; the daily limit, or the key's own, is 429 until midnight UTC, and so is a plan's month of emails used up; a bad or missing key is 401.
Event fields No ip, tls, attempt, pool, asm_group_id or marketing_campaign_*.
Inbound Parse fields No dkim, SPF, sender_ip or spam score.
Webhook retries Failed deliveries are retried for about 45 hours, then marked Dead and can be replayed (Webhooks). Each attempt must finish within 10 seconds: answer 2xx at once and do the work afterward.

The developer reference, field by field: SendGrid compatibility.