# Mailgun

Lightweight, dependency-free, in-memory Mailgun API v3 fake for testing code that uses the real `mailgun.js` SDK (or the language-agnostic Mailgun REST API).

Default port: `4826`

## Quick start

Start the server:

```js
import { MailgunServer } from "./services/mailgun/src/server.js";

const server = new MailgunServer(4826);
await server.start();
// ... run your app/tests ...
await server.stop();
```

Point the real `mailgun.js` client at it:

```js
import formData from "form-data";
import Mailgun from "mailgun.js";

const mailgun = new Mailgun(formData);
const mg = mailgun.client({
  username: "api",
  key: "key-parlel",
  url: "http://127.0.0.1:4826", // point at the parlel fake
});

const result = await mg.messages.create("sandbox.parlel", {
  from: "Excited User <mailgun@sandbox.parlel>",
  to: ["user@parlel.dev"],
  subject: "Hello",
  text: "Testing some Mailgun awesomeness!",
});
// result => { id: "<...@sandbox.parlel>", message: "Queued. Thank you." }
```

Messages are POSTed as `application/x-www-form-urlencoded` (or `multipart/form-data`) form fields. Every send is captured and inspectable via `/__parlel/*`.

## Access via MCP / preview URL

When run under the parlel pool, this service is reachable through the MCP gateway
and a preview URL is exposed at `http://127.0.0.1:4826`. Use `MAILGUN_BASE_URL`
to point clients/agents at it. Captured mail is available at
`GET /__parlel/messages` for assertions without ever delivering real email.

## Implemented operations

All `/v3/*` routes require HTTP Basic auth (`api:key-...`). State is in-memory and ephemeral.

- `POST /v3/:domain/messages` — send a message. Parses `from,to,subject,text,html` form fields, captures the message, returns `{ id, message: "Queued. Thank you." }`. Multiple `to=` values collapse into an array.
- `GET /v3/:domain/events` — list delivery events (one `accepted` event is recorded per send).
- `GET /v3/:domain/mailing_lists` — list mailing lists.
- `POST /v3/:domain/mailing_lists` — create a mailing list (`address` required).
- `GET /v3/domains` — list domains (a seeded `sandbox.parlel` always exists).

### Service & inspection operations (parlel extensions)

- `GET /` — service metadata.
- `GET /health` — health check (`{ status: "ok" }`).
- `POST /__parlel/reset` — reset all in-memory state.
- `GET /__parlel/messages` — list captured messages (`{ messages, count }`).
- `GET /__parlel/messages/:id` — fetch a single captured message (id with or without angle brackets).
- `DELETE /__parlel/messages` — clear only the captured mailbox.

## Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

| Feature | Status |
| --- | --- |
| `messages.create` (urlencoded + multipart form) | ✅ Supported |
| Events listing | ✅ Supported (synthesized `accepted` events) |
| Mailing lists (create/list) | ✅ Supported |
| Domains listing | ✅ Supported |
| Captured-mail inspection | ✅ Supported (parlel extension) |
| Actual email delivery / SMTP | ✓ By design — Captured in-memory for inspection — no real messages sent |
| Attachments / inline file uploads | ◐ Accepted as form fields, not stored as binaries |
| Webhooks / routes / templates / stats analytics | ⟳ Roadmap |
| Real API-key validity / scope enforcement | ✓ By design — Any non-empty credential is accepted — no real secrets needed |
| Rate limiting (`429`) | ✓ By design — Never throttles — local tests run at full speed, zero cost |

## Error shapes

Errors use the Mailgun envelope `{ "message": "..." }`.

| Status | When |
| --- | --- |
| `400` | missing `from`/`to`/`address` parameter, bad body |
| `401` | missing/invalid Basic auth |
| `404` | unknown endpoint or captured message |
| `405` | method not allowed for the path |

## Manifest

See `services/mailgun/manifest.json`:

- name: `mailgun`, image: `parlel/mailgun:1.0`
- port: `4826`, protocol: `http`, healthcheck: `/health`, startup ≈ 100ms
- env: `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`, `MAILGUN_BASE_URL`
