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

# Migrating from Mailgun

> How to migrate from Mailgun to Mailofly: replacing legacy multipart form-data requests with clean JSON APIs and modern webhooks.

# Migrating from Mailgun to Mailofly

Mailgun has been a legacy email provider for over a decade, but modern teams often find its `multipart/form-data` API design awkward, its domain-scoped URL paths clunky (`/v3/DOMAIN_NAME/messages`), and its billing tiers unpredictable.

Mailofly simplifies email sending with clean REST JSON endpoints, typed modern SDKs, predictable pricing, and instant real-time logs.

***

## Key Differences at a Glance

| Aspect             | Mailgun                                    | Mailofly                                        |
| :----------------- | :----------------------------------------- | :---------------------------------------------- |
| **API Format**     | `multipart/form-data` or URL-encoded       | Clean JSON (`application/json`)                 |
| **Endpoint URL**   | Domain-dependent (`/v3/{domain}/messages`) | Uniform URL (`https://api.mailofly.com/emails`) |
| **Authentication** | HTTP Basic Auth (`api:YOUR_API_KEY`)       | Standard Bearer Token (`Bearer mf_live_...`)    |
| **Subaccounts**    | Subaccounts requiring dedicated setup      | Sending Identities with `account_key`           |
| **AI Integration** | None                                       | Official `@mailofly/mcp` Server                 |

***

## 1. Code Comparison

### Node.js / TypeScript

<CodeGroup>
  ```ts Before: Mailgun (mailgun.js) theme={null}
  import FormData from "form-data";
  import Mailgun from "mailgun.js";

  const mailgun = new Mailgun(FormData);
  const mg = mailgun.client({
    username: "api",
    key: process.env.MAILGUN_API_KEY!,
  });

  const response = await mg.messages.create("mg.yourdomain.com", {
    from: "Excited User <mailgun@yourdomain.com>",
    to: ["test@example.com"],
    subject: "Hello from Mailgun",
    text: "Testing some Mailgun awesomeness!",
    html: "<h1>Testing some Mailgun awesomeness!</h1>",
  });

  console.log("Mailgun ID:", response.id);
  ```

  ```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({
    from: "Acme <hello@yourdomain.com>",
    to: ["test@example.com"],
    subject: "Hello from Mailofly",
    text: "Testing clean email delivery!",
    html: "<h1>Testing clean email delivery!</h1>",
  });

  console.log("Mailofly ID:", id);
  ```
</CodeGroup>

***

### Python

<CodeGroup>
  ```python Before: Mailgun (requests) theme={null}
  import requests
  import os

  response = requests.post(
      "https://api.mailgun.net/v3/yourdomain.com/messages",
      auth=("api", os.environ["MAILGUN_API_KEY"]),
      data={
          "from": "Acme <hello@yourdomain.com>",
          "to": ["test@example.com"],
          "subject": "Hello",
          "text": "Testing Mailgun",
      },
  )
  ```

  ```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 <hello@yourdomain.com>",
      to=["test@example.com"],
      subject="Hello",
      text="Testing Mailofly",
  )
  ```
</CodeGroup>

***

### cURL

<CodeGroup>
  ```bash Before: Mailgun (Form-Data & Basic Auth) theme={null}
  curl -s --user 'api:YOUR_MAILGUN_KEY' \
    https://api.mailgun.net/v3/yourdomain.com/messages \
    -F from='Excited User <mailgun@yourdomain.com>' \
    -F to=test@example.com \
    -F subject='Hello' \
    -F text='Testing Mailgun'
  ```

  ```bash After: Mailofly (Standard JSON & Bearer Token) theme={null}
  curl -X POST https://api.mailofly.com/emails \
    -H "Authorization: Bearer mf_live_xxxxxxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "from": "Excited User <mailgun@yourdomain.com>",
      "to": ["test@example.com"],
      "subject": "Hello",
      "text": "Testing Mailofly"
    }'
  ```
</CodeGroup>

***

## 2. Migrating Suppressions (Bounces & Complaints)

Mailgun tracks three suppression lists:

1. **Bounces**: `/v3/{domain}/bounces`
2. **Unsubscribes**: `/v3/{domain}/unsubscribes`
3. **Complaints**: `/v3/{domain}/complaints`

### Export from Mailgun

1. Log in to the Mailgun control panel.
2. Go to **Sending → Suppressions**.
3. Select your domain and download the CSVs for Bounces, Unsubscribes, and Complaints.

### Import into Mailofly

1. In Mailofly, navigate to **[Audiences > Contacts](https://www.mailofly.com/user/audience)**.
2. Click **Import CSV**.
3. Select your suppression files and check **Mark as Suppressed / Unsubscribed**.
4. Mailofly will ensure no further emails are dispatched to these addresses.

***

## 3. Webhook Migration

Mailgun requires calculating an HMAC-SHA256 digest on `timestamp + token` using your Mailgun signing key. Mailofly uses standard Svix / HMAC webhooks.

### Event Name Mapping

| Mailgun Event                  | Mailofly Event           | Description                       |
| :----------------------------- | :----------------------- | :-------------------------------- |
| `accepted`                     | `email.sent`             | Message accepted by Mailofly      |
| `delivered`                    | `email.delivered`        | Accepted by recipient ISP         |
| `failed` (severity: permanent) | `email.bounced`          | Hard bounce                       |
| `failed` (severity: temporary) | `email.delivery_delayed` | Soft bounce or temporary throttle |
| `complained`                   | `email.complained`       | Recipient marked as spam          |
| `opened`                       | `email.opened`           | Tracking pixel loaded             |
| `clicked`                      | `email.clicked`          | Recipient clicked link            |
| `unsubscribed`                 | `contact.unsubscribed`   | Recipient opted out               |

***

## Migration Checklist

* [ ] Add domain to Mailofly and configure DKIM (`mailofly._domainkey`) and SPF.
* [ ] Add `include:mailofly.com` to your domain SPF record.
* [ ] Export Mailgun Bounces, Complaints, and Unsubscribes, and import into Mailofly.
* [ ] Update client application code from `multipart/form-data` to `@mailofly/node` JSON calls.
* [ ] Configure Mailofly webhook URLs for delivery and bounce tracking.
* [ ] Verify test emails on the Mailofly dashboard.
* [ ] Remove Mailgun MX and SPF records after the transition is complete.


## Related topics

- [Migrating from Resend](/guides/migrations/resend.md)
- [Migrating from Postmark](/guides/migrations/postmark.md)
- [Migrating from Amazon SES](/guides/migrations/aws-ses.md)
- [Migrating from Mailchimp & Mandrill](/guides/migrations/mailchimp.md)
- [Migrating from Twilio SendGrid](/guides/migrations/sendgrid.md)
