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

# Mailgun

> Client-mode REST API connector for Mailgun: send transactional email from plant events, follow delivery through events, logs and metrics, keep suppression lists in step, manage mailing lists, and point Mailgun webhooks at a Coreflux server connector

Technical Preview

## Overview

A client-mode connector for the [Mailgun API](https://documentation.mailgun.com/docs/mailgun/api-reference/) (Mailgun by Sinch) that sends email from plant data and keeps track of what happened to it. Typical uses are shift reports, alarm digests, maintenance and quality notices, and audit copies, tagged per plant or line so you can filter delivery events and metrics later. The connector also reads and adds suppressions (bounces, unsubscribes, complaints), manages mailing lists for each plant or team, and registers Mailgun webhooks that post delivery events back to a Coreflux server connector.

One connector carries 33 events (21 GET, 9 POST, 3 PUT; 9 writes, no DELETE). Topics use `messaging/mailgun/<object>/<verb>`; each response is published on `<source topic>/response`.

> [!NOTE]
>
> The template was checked against Mailgun's [OpenAPI definition](https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun) and a local mock, and every endpoint was probed on `api.mailgun.net` with a dummy key (each answers Mailgun's authentication error, while unknown paths answer 404). It has **not** been run with a real API key yet, so status stays blocked until an authenticated smoke passes.

## Setup

1. In the Mailgun dashboard, copy your primary account API key (Account Settings, API Keys), or create a domain sending key on the domain's settings page. A sending key can only call `MessageSend`; every other event needs the account key.
2. Use a verified sending domain such as `mg.example.com`. EU domains change `BASE_ADDRESS` to `https://api.eu.mailgun.net`, because a domain lives in one region only.
3. Keep the key in a broker secret (for example `MAILGUN_API_KEY`) and build the header value in a LoT action with `GET SECRET`. Never retain topics that carry it.

## Authentication

Mailgun uses HTTP Basic auth with the literal username `api` and the API key as the password. The connector sends `Authorization: Basic <value>` and takes the value from the payload field `basic_auth`, which is base64 of `api:<key>`:

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"basic_auth": "<base64 of api:YOUR_API_KEY>", "domain": "mg.example.com"}
```

## Form and JSON bodies

Sending a message, resending a stored message, creating or updating a webhook and adding or updating a list member take form fields. Pass them URL-encoded in `form`; the connector sends them as `application/x-www-form-urlencoded`. Mailgun options use prefixes such as `o:tag`, `o:testmode`, `h:Reply-To` and `v:asset`, so encode the colon as `%3A`. Suppression adds and the analytics queries (`LogsQuery`, `MetricsQuery`, `UsageMetricsQuery`) take JSON in `body`.

> [!WARNING]
>
> Mailgun documents its send endpoint with `multipart/form-data`. This template sends URL-encoded form fields, which carry the same text fields; confirm with `o:testmode=yes` on your account before relying on it. Attachments and inline images need multipart and are not packaged.

## Examples

Email maintenance when a press overheats (`messaging/mailgun/messages/send`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"basic_auth": "<base64>", "domain": "mg.example.com", "form": "from=Coreflux%20Porto%20%3Calerts%40mg.example.com%3E&to=maintenance%40example.com&subject=Press%207%20overtemperature&text=Press%207%20at%2094%20C%20on%20line%202&o%3Atag=plant-porto&v%3Aasset=press7"}
```

Mailgun answers with the queued message id, for example `{"id": "<20261005...@mg.example.com>", "message": "Queued. Thank you."}`.

Find failed deliveries from the last day (`messaging/mailgun/events/list`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"basic_auth": "<base64>", "domain": "mg.example.com", "query": "event=failed&limit=25"}
```

Check whether a technician's address bounced before you rely on it (`messaging/mailgun/bounces/get`; a 404 means it is not suppressed):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"basic_auth": "<base64>", "domain": "mg.example.com", "address": "tech@example.com"}
```

Send permanent failures to a Coreflux server connector (`messaging/mailgun/webhooks/create`):

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{"basic_auth": "<base64>", "domain": "mg.example.com", "form": "id=permanent_fail&url=https%3A%2F%2Fbroker.example.com%2Fmailgun%2Fevents"}
```

Values in `query` and `form` are sent as given, so URL-encode them. Email addresses in path fields (`address`, `list`, `member`) are used as they are.

## Events

| Event | Method | Endpoint | Source topic | Write | Notes |
| - | - | - | - | - | - |
| `DomainsList` | `GET` | `/v4/domains?{value.json.query}` | `messaging/mailgun/domains/list` | | Smoke: sending domains on the account with state (active, unverified). query e.g. limit=100\&state=active (or empty). |
| `DomainGet` | `GET` | `/v4/domains/{value.json.domain}` | `messaging/mailgun/domains/get` | | One domain with its DNS sending and receiving records and their validity. |
| `DomainVerify` | `PUT` | `/v4/domains/{value.json.domain}/verify` | `messaging/mailgun/domains/verify` | Yes | Re-check the domain's DNS records now (no body). Returns the domain and record validity. |
| `DomainTrackingGet` | `GET` | `/v3/domains/{value.json.domain}/tracking` | `messaging/mailgun/domains/tracking/get` | | Open, click and unsubscribe tracking settings for the domain. |
| `SendingQueuesGet` | `GET` | `/v3/domains/{value.json.domain}/sending_queues` | `messaging/mailgun/domains/sending-queues/get` | | Scheduled and regular sending queue status for the domain (is mail being held back). |
| `MessageSend` | `POST` | `/v3/{value.json.domain}/messages` | `messaging/mailgun/messages/send` | Yes | Catalog 'POST messages'. form (URL-encoded): from, to (repeat or comma-separate), subject and one of text, html or template; optional cc, bcc, o:tag (repeatable, e.g. plant-porto), o:testmode=yes (accepted but not delivered), o:deliverytime (RFC 2822), h:Reply-To, v:asset=press7 (custom variable, echoed in events and webhooks), t:variables (JSON for templates). Options o:/h:/v:/t: max 16 KB total. Returns \{"id":"\<[...@mg.example.com](mailto:...@mg.example.com)>","message":"Queued. Thank you."}. Attachments need multipart and are not packaged. |
| `StoredMessageGet` | `GET` | `/v3/domains/{value.json.domain}/messages/{value.json.storage_key}` | `messaging/mailgun/messages/stored/get` | | A stored message (kept about 3 days) by storage key from an event or webhook (storage.key). |
| `StoredMessageResend` | `POST` | `/v3/domains/{value.json.domain}/messages/{value.json.storage_key}` | `messaging/mailgun/messages/stored/resend` | Yes | Resend a stored message to new recipients. form: to=shift-lead%40example.com. |
| `EventsList` | `GET` | `/v3/{value.json.domain}/events?{value.json.query}` | `messaging/mailgun/events/list` | | Catalog 'GET events'. Delivery events (accepted, delivered, failed, opened, clicked, unsubscribed, complained, stored), kept 3+ days. query e.g. event=failed\&limit=25\&begin=Mon%2C%2005%20Oct%202026%2000%3A00%3A00%20%2B0000\&ascending=yes, or message-id=\<id> or recipient=ops%40example.com. Page with paging.next URL parameters. |
| `LogsQuery` | `POST` | `/v1/analytics/logs` | `messaging/mailgun/analytics/logs/query` | | Account-wide event logs (the newer replacement for domain events). body: \{"duration":"1d","events":\["failed","delivered"],"filter":\{"AND":\[\{"attribute":"domain","comparator":"=","values":\[\{"label":"mg.example.com","value":"mg.example.com"}]}]},"pagination":\{"limit":25}}. Read-only POST. |
| `MetricsQuery` | `POST` | `/v1/analytics/metrics` | `messaging/mailgun/analytics/metrics/query` | | Sending metrics (replaces the deprecated v3 stats). body: \{"resolution":"day","duration":"7d","dimensions":\["time"],"metrics":\["sent\_count","delivered\_count","failed\_count","opened\_count"],"filter":\{"AND":\[\{"attribute":"domain","comparator":"=","values":\[\{"label":"mg.example.com","value":"mg.example.com"}]}]},"include\_aggregates":true}. Read-only POST. |
| `UsageMetricsQuery` | `POST` | `/v1/analytics/usage/metrics` | `messaging/mailgun/analytics/usage/query` | | Account usage (processed, email validation, inbox placement counts). body: \{"resolution":"month","duration":"3M","metrics":\["processed\_count"]}. Read-only POST. |
| `BouncesList` | `GET` | `/v3/{value.json.domain}/bounces?{value.json.query}` | `messaging/mailgun/bounces/list` | | Suppressed bounced addresses. query e.g. limit=100 (page with paging URLs). |
| `BounceGet` | `GET` | `/v3/{value.json.domain}/bounces/{value.json.address}` | `messaging/mailgun/bounces/get` | | Is this address on the bounce list (404 means no)? Check before alerting a contact. |
| `BouncesAdd` | `POST` | `/v3/{value.json.domain}/bounces` | `messaging/mailgun/bounces/add` | Yes | body (JSON array, up to 1000): \[\{"address":"[old-tech@example.com](mailto:old-tech@example.com)","code":"550","error":"Mailbox removed"}]. |
| `UnsubscribesList` | `GET` | `/v3/{value.json.domain}/unsubscribes?{value.json.query}` | `messaging/mailgun/unsubscribes/list` | | Unsubscribed addresses. query e.g. limit=100. |
| `UnsubscribeGet` | `GET` | `/v3/{value.json.domain}/unsubscribes/{value.json.address}` | `messaging/mailgun/unsubscribes/get` | | Unsubscribe record for one address (tags it applies to); 404 if none. |
| `UnsubscribesAdd` | `POST` | `/v3/{value.json.domain}/unsubscribes` | `messaging/mailgun/unsubscribes/add` | Yes | body (JSON array): \[\{"address":"[visitor@example.com](mailto:visitor@example.com)","tags":\["plant-tours"]}] (tags omitted = all mail). |
| `ComplaintsList` | `GET` | `/v3/{value.json.domain}/complaints?{value.json.query}` | `messaging/mailgun/complaints/list` | | Spam complaints. query e.g. limit=100. |
| `ComplaintGet` | `GET` | `/v3/{value.json.domain}/complaints/{value.json.address}` | `messaging/mailgun/complaints/get` | | Complaint record for one address; 404 if none. |
| `WebhooksList` | `GET` | `/v3/domains/{value.json.domain}/webhooks` | `messaging/mailgun/webhooks/list` | | Domain webhooks by event type (accepted, delivered, permanent\_fail, temporary\_fail, opened, clicked, unsubscribed, complained). |
| `WebhookGet` | `GET` | `/v3/domains/{value.json.domain}/webhooks/{value.json.webhook}` | `messaging/mailgun/webhooks/get` | | URLs for one webhook type. webhook e.g. permanent\_fail. |
| `WebhookCreate` | `POST` | `/v3/domains/{value.json.domain}/webhooks` | `messaging/mailgun/webhooks/create` | Yes | Catalog 'server connector for webhook': point a webhook type at a Coreflux ENABLE\_SERVER REST\_API connector. form: id=permanent\_fail\&url=https%3A%2F%2Fbroker.example.com%2Fmailgun%2Fevents (up to 3 url fields). |
| `WebhookUpdate` | `PUT` | `/v3/domains/{value.json.domain}/webhooks/{value.json.webhook}` | `messaging/mailgun/webhooks/update` | Yes | Replace the URLs of one webhook type. form: url=https%3A%2F%2Fbroker.example.com%2Fmailgun%2Fevents. |
| `TemplatesList` | `GET` | `/v3/{value.json.domain}/templates?{value.json.query}` | `messaging/mailgun/templates/list` | | Stored templates on the domain. query e.g. limit=50. |
| `TemplateGet` | `GET` | `/v3/{value.json.domain}/templates/{value.json.template}?{value.json.query}` | `messaging/mailgun/templates/get` | | One template. query active=yes includes the active version content (or empty). |
| `MailingListsList` | `GET` | `/v3/lists/pages?{value.json.query}` | `messaging/mailgun/lists/list` | | Mailing lists on the account. query e.g. limit=100. |
| `MailingListGet` | `GET` | `/v3/lists/{value.json.list}` | `messaging/mailgun/lists/get` | | One mailing list. list = list address, e.g. [porto-shift-leads@mg.example.com](mailto:porto-shift-leads@mg.example.com). |
| `ListMembersList` | `GET` | `/v3/lists/{value.json.list}/members/pages?{value.json.query}` | `messaging/mailgun/lists/members/list` | | Members of a list. query e.g. subscribed=yes\&limit=100. |
| `ListMemberAdd` | `POST` | `/v3/lists/{value.json.list}/members` | `messaging/mailgun/lists/members/add` | Yes | form: address=tech%40example.com\&name=Ana%20Silva\&vars=%7B%22line%22%3A%222%22%7D\&subscribed=yes\&upsert=yes. |
| `ListMemberUpdate` | `PUT` | `/v3/lists/{value.json.list}/members/{value.json.member}` | `messaging/mailgun/lists/members/update` | Yes | form: subscribed=no (or name, vars). member = member email address. |
| `RoutesList` | `GET` | `/v3/routes?{value.json.query}` | `messaging/mailgun/routes/list` | | Inbound connectors (forward incoming mail, e.g. to a Coreflux server connector). query e.g. limit=100\&skip=0. |
| `RouteMatch` | `GET` | `/v3/routes/match?{value.json.query}` | `messaging/mailgun/routes/match` | | Which inbound connector matches an address. query: address=maintenance%40mg.example.com. |

## Limits

Send options (`o:`, `h:`, `v:` and `t:` fields) are limited to 16 KB per message. Events and stored messages are kept for at least 3 days (longer on paid plans). Mailgun rate-limits per account and answers `429` when you send too fast; sandbox domains can only send to authorized recipients. The v3 stats and tag endpoints are deprecated, so this template uses the `/v1/analytics` metrics and logs API instead.

## Not packaged

* All DELETE endpoints (domains, webhooks, suppressions, templates, lists, members, connectors) on purpose.
* Attachments, inline images and MIME sending (`messages.mime`), which need multipart.
* Domain creation and DNS, DKIM keys, IP pools and dedicated IPs, subaccounts, API keys, SMTP credentials, users, alerts, template and connector creation, bulk member uploads and suppression imports.
* Deprecated v3 stats, tags and aggregates; email validation and inbox placement (separate Mailgun Optimize API).
* The incoming webhook direction: Mailgun webhooks post event JSON to a separate `ENABLE_SERVER` REST\_API connector, which should check Mailgun's HMAC signature with the webhook signing key.

## Suggested branches

`feat/rest-api-route-mailgun` for the broker template and `feat/docs-rest-api-mailgun` for this page.


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