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

# Migrate from Mailgun

> Move a Mailgun app to Hiveku over SMTP

Hiveku has no Mailgun-compatible API. A Mailgun app moves to Hiveku over [SMTP](https://docs.hiveku.com/email/smtp.md):

* An app that already sends through Mailgun's SMTP changes its SMTP settings, and nothing else.
* An app that calls Mailgun's HTTP API replaces those calls with an SMTP client, as in the examples below. It can also call Hiveku's [REST API](https://docs.hiveku.com/email/sending.md) instead.

## Before you switch

**1. Verify your domain in Hiveku**

Add the domain you send from and publish its records. Keep Mailgun's DNS records until the switch is done: Hiveku's records sit beside them. See [Moving from another provider](https://docs.hiveku.com/email/domains.md#moving-from-another-provider).

If Mailgun also receives mail for the domain, keep its MX records. Hiveku's Email Service sends email; it does not receive it for your app.

**2. Create a key**

Create a **Production** key in **Settings > API Keys**, with **Send Emails**. It is your SMTP password. See [API keys](https://docs.hiveku.com/email/api-keys.md).

**3. Recreate your webhooks**

Add your endpoint as a Hiveku [webhook](https://docs.hiveku.com/email/webhooks.md). Hiveku's event names, payloads and signatures are its own, so update your handler to read them.

**4. Bring over your suppressions**

Hiveku suppresses an address on its own after a hard bounce or a spam complaint, but it does not know who bounced, complained or unsubscribed at Mailgun. Add those addresses before you switch. See [Suppressions](#suppressions).

## Change your SMTP settings

Replace the host, port, username and password with these:

| Setting | Value |
| - | - |
| Host | `smtp.hiveku.com` |
| Port | `465` with TLS from the start, or `587` with STARTTLS |
| Username | `apikey` |
| Password | Your Hiveku key, `hk_live_...` |

The sender address must be on your verified domain.

## Examples

These send the same email: a reply-to address, a text and an HTML part, and an attachment. If your app called Mailgun's HTTP API, a call like this replaces it.

**Nodemailer**

```ts theme={null}
import fs from "node:fs";
import nodemailer from "nodemailer";

const transporter = nodemailer.createTransport({
  host: "smtp.hiveku.com",
  port: 465,
  secure: true,
  auth: { user: "apikey", pass: process.env.HIVEKU_EMAIL_API_KEY },
});

const info = await transporter.sendMail({
  from: "Acme <billing@mail.acme.com>",
  to: "customer@example.com",
  replyTo: "support@acme.com",
  subject: "Your invoice #1024",
  text: "Your invoice is attached.",
  html: "<p>Your invoice is attached.</p>",
  attachments: [
    { filename: "invoice-1024.pdf", content: fs.readFileSync("invoice-1024.pdf") },
  ],
});

console.log(info.response); // 250 Message queued: <the email's id>
```

**Python**

```python theme={null}
import os
import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "Acme <billing@mail.acme.com>"
msg["To"] = "customer@example.com"
msg["Reply-To"] = "support@acme.com"
msg["Subject"] = "Your invoice #1024"
msg.set_content("Your invoice is attached.")
msg.add_alternative("<p>Your invoice is attached.</p>", subtype="html")

with open("invoice-1024.pdf", "rb") as f:
    msg.add_attachment(f.read(), maintype="application", subtype="pdf", filename="invoice-1024.pdf")

with smtplib.SMTP_SSL("smtp.hiveku.com", 465) as smtp:
    smtp.login("apikey", os.environ["HIVEKU_EMAIL_API_KEY"])
    smtp.send_message(msg)
```

## What changes

* **Provider headers.** If your messages carry `X-Mailgun-` headers, remove them. Hiveku does not read them, and it passes `X-` headers on to recipients as they are.
* **Tags.** SMTP messages carry no tags. If you filter events by tag, send those emails through the [REST API](https://docs.hiveku.com/email/sending.md#tags).
* **Scheduling.** Hiveku cannot hold a message for later. Send it when it should go.
* **Templates.** Build the message in your app, or send a Hiveku [template](https://docs.hiveku.com/email/sending.md#send-with-a-template) through the REST API.
* **Recipients.** At most 50 per message. Recipients not named in the To or Cc header get a hidden copy, as Bcc.
* **Size.** At most 25 MB per message.
* **Test keys.** A test key's messages are accepted and simulated: the reply says nothing was delivered.

When Hiveku refuses a message, the SMTP reply code says whether to retry. See [Replies](https://docs.hiveku.com/email/smtp.md#replies).

## Suppressions

Add each address that bounced, complained or unsubscribed at Mailgun with `POST /api/v1/email/suppressions`:

```bash theme={null}
curl -X POST https://app.hiveku.com/api/v1/email/suppressions \
  -H "Authorization: Bearer $HIVEKU_SUPPRESSIONS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "bounced@example.com", "reason": "Bounced at Mailgun" }'
```

It takes one address per request, answers `201`, and answers `409 already_exists` for an address already on the list. The key needs the `email:suppressions` permission. The dashboard's key form does not offer it, but your AI assistant can create such a key.

## After the switch

* Watch **Settings > Email Service > Email Logs**, in the **App sends (API and SMTP)** view, as your app's email moves over.
* Test with a test key, not a trickle of real sends. Simulated sends are not counted, so they do not set a small usual day for the [daily volume check](https://docs.hiveku.com/email/api-keys.md#daily-volume-check). If your account already sends API mail on most days, moving a large app over in one go can pass 5 times that usual day and hold API and SMTP mail until 00:00 UTC.
* Once nothing sends through Mailgun, remove its DNS records for sending and its `include:` from your SPF record. Keep its MX records if it still receives mail for you.


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