> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from Twilio SendGrid

> How to migrate from Twilio SendGrid to Mailofly: replacing complex v3 personalizations, SMTP relay cutover, and suppression migration.

# Migrating from Twilio SendGrid to Mailofly

SendGrid has long been an industry standard for email delivery, but developers frequently struggle with its deeply nested `personalizations` API structure, dated dashboard interface, slow customer support, and complex pricing tiers.

Mailofly offers an elegant, high-throughput modern replacement with transparent pricing, instant logs, clean APIs, and zero payload nesting.

***

## Why Developers Migrate from SendGrid

* **Simplified Payload Schema**: Say goodbye to `personalizations[0].to[0].email` and multiple `content` objects. Mailofly uses flat, intuitive JSON payloads.
* **Instant Delivery Logs**: Real-time log streaming with granular status codes, bounce categorization, and click tracking—no 15-minute dashboard delays.
* **Modern Developer Ergonomics**: Modern typed SDKs for TypeScript, Python, Go, PHP, and Dart, plus native Model Context Protocol (MCP) support for AI.
* **Clean Identity Routing**: Replace cumbersome SendGrid Subusers with lightweight Mailofly Sending Identities (`account_key`).

***

## 1. Payload Schema Comparison

SendGrid's v3 `/v3/mail/send` payload requires deep nesting. Here is how it maps directly to Mailofly:

| SendGrid v3 Field | Mailofly Field | Notes |
| :- | :- | :- |
| `personalizations[0].to` | `to` | Array of strings (`["user@example.com"]`) |
| `personalizations[0].subject` | `subject` | Top-level string |
| `from.email` + `from.name` | `from` | Format: `"Acme <sales@acme.com>"` |
| `content[].value` (type: text/html) | `html` | Direct HTML string |
| `content[].value` (type: text/plain) | `text` | Direct plaintext string |
| `personalizations[0].dynamic_template_data` | `variables` | Plain key-value object |
| `custom_args` | `tags` or `headers` | Metadata dictionary |
| `attachments` (base64) | `attachments` | Array of `{ filename, content }` objects |

***

## 2. Code Migration Examples

### Node.js / TypeScript

```ts Before: SendGrid (@sendgrid/mail) theme={null}
import sgMail from "@sendgrid/mail";

sgMail.setApiKey(process.env.SENDGRID_API_KEY!);

const msg = {
  to: "alex@example.com",
  from: { email: "team@acme.com", name: "Acme Team" },
  subject: "Welcome to Acme",
  text: "Hello Alex, welcome to Acme!",
  html: "<strong>Hello Alex</strong>, welcome to Acme!",
  customArgs: { customerId: "12345" },
};

await sgMail.send(msg);
```

```ts After: Mailofly (@mailofly/node) theme={null}
import { Mailofly } from "@mailofly/node";

const mailofly = new Mailofly({
  apiKey: process.env.MAILOFLY_API_KEY!,
});

const { id } = await mailofly.emails.send({
  to: ["alex@example.com"],
  from: "Acme Team <team@acme.com>",
  subject: "Welcome to Acme",
  text: "Hello Alex, welcome to Acme!",
  html: "<strong>Hello Alex</strong>, welcome to Acme!",
  tags: [{ name: "customerId", value: "12345" }],
});

console.log("Sent with Mailofly:", id);
```

***

### Python

```python Before: SendGrid theme={null}
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
import os

message = Mail(
    from_email="team@acme.com",
    to_emails="alex@example.com",
    subject="Welcome to Acme",
    html_content="<strong>Welcome to Acme!</strong>",
)

sg = SendGridAPIClient(os.environ.get("SENDGRID_API_KEY"))
response = sg.send(message)
```

```python After: Mailofly theme={null}
from mailofly import Mailofly
import os

client = Mailofly(api_key=os.environ["MAILOFLY_API_KEY"])

response = client.emails.send(
    sender="Acme Team <team@acme.com>",
    to=["alex@example.com"],
    subject="Welcome to Acme",
    html="<strong>Welcome to Acme!</strong>",
)

print("Mailofly ID:", response.id)
```

***

### Raw HTTP cURL

Notice the reduction in nesting and boilerplate:

```bash Before: SendGrid v3 theme={null}
curl -X POST https://api.sendgrid.com/v3/mail/send \
  -H "Authorization: Bearer SG.xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "personalizations": [
      {
        "to": [{ "email": "alex@example.com" }]
      }
    ],
    "from": { "email": "team@acme.com", "name": "Acme Team" },
    "subject": "Your Order Confirmation",
    "content": [
      {
        "type": "text/html",
        "value": "<p>Thank you for your order!</p>"
      }
    ]
  }'
```

```bash After: Mailofly theme={null}
curl -X POST https://api.mailofly.com/emails \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme Team <team@acme.com>",
    "to": ["alex@example.com"],
    "subject": "Your Order Confirmation",
    "html": "<p>Thank you for your order!</p>"
  }'
```

***

## 3. SMTP Drop-in Cutover

If you use SendGrid via SMTP (such as in Laravel, Supabase, Ghost, WordPress, or Postfix), migration requires **zero code changes**—simply update your environment variables:

| Setting | SendGrid | Mailofly |
| :- | :- | :- |
| **Host** | `smtp.sendgrid.net` | `smtp.mailofly.com` |
| **Port** | `587` (TLS) or `465` (SSL) | `587` (STARTTLS) or `465` (SSL/TLS) |
| **Username** | `apikey` | `mailofly` (or your API Key) |
| **Password** | SendGrid API Key (`SG...`) | Mailofly API Key (`mf_live_...`) |

***

## 4. Migrating Suppressions & Unsubscribes

Failing to import your SendGrid suppression lists before sending through Mailofly can cause high initial bounce rates, triggering automated rate limits.

SendGrid maintains four suppression tables:

1. **Bounces**: Mailboxes that permanently rejected mail.
2. **Spam Reports**: Contacts who marked your mail as spam.
3. **Blocks**: IPs or domains temporarily blocked.
4. **Global Unsubscribes**: Contacts who opted out of all emails.

### Export from SendGrid

In the SendGrid Dashboard:

1. Go to **Settings → Suppressions** (or use the SendGrid Suppressions API: `/v3/suppression/bounces`, `/v3/suppression/spam_reports`, `/v3/suppression/unsubscribes`).
2. Export each list as a CSV.

### Import to Mailofly

1. In Mailofly, go to **[Audiences > Contacts](https://www.mailofly.com/user/audience)**.
2. Click **Import Contacts**.
3. Upload your CSV and toggle **Mark as Unsubscribed / Suppressed**.
4. Mailofly will automatically populate your global suppression blacklist, protecting your domain's sending reputation.

***

## 5. Webhook Migration

SendGrid dispatches webhooks as an unauthenticated or signature-checked **JSON array of events**. Mailofly sends structured, single-event payloads cryptographically signed with HMAC / Svix.

### Event Type Equivalents

| SendGrid Event | Mailofly Event | Meaning |
| :- | :- | :- |
| `processed` | `email.sent` | Email accepted and queued by the engine |
| `delivered` | `email.delivered` | Accepted by recipient's mail server |
| `bounce` | `email.bounced` | Hard bounce (permanent rejection) |
| `deferred` | `email.delivery_delayed` | Soft bounce or temporary ISP throttle |
| `spamreport` | `email.complained` | Recipient marked message as spam |
| `open` | `email.opened` | Tracking pixel loaded by recipient |
| `click` | `email.clicked` | Recipient clicked a tracked link |
| `unsubscribe` | `contact.unsubscribed` | Recipient opted out via unsubscribe link |

***

## 6. Migration Checklist

* [ ] Add domain to Mailofly and add `mailofly._domainkey` DKIM records.
* [ ] Add `include:mailofly.com` to your existing SPF TXT record alongside SendGrid.
* [ ] Export SendGrid Bounces, Spam Reports, and Unsubscribes, and import into Mailofly Suppressions.
* [ ] Update SDK packages or environment variables (`MAILOFLY_API_KEY`, `smtp.mailofly.com`).
* [ ] Set up Mailofly Webhook endpoints in your application.
* [ ] Send test emails and verify click/open tracking on the Mailofly dashboard.
* [ ] Decommission the SendGrid API key once traffic has completely transitioned.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.