# How to Implement Transactional Email Sending via the Listmonk API

> Implement transactional email sending with the Listmonk API. Learn how to POST to /api/tx with your API key and template ID to queue and deliver messages.

- Repository: [Kailash Nadh/listmonk](https://github.com/knadh/listmonk)
- Tags: api-reference
- Published: 2026-05-19

---

**Send transactional emails through Listmonk by POSTing to `/api/tx` with an API key possessing the `tx:send` permission, specifying a template ID and recipient data, and letting the handler in [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go) render and queue the message for SMTP delivery.**

Listmonk treats a **transactional (TX) email** as a single-message push rendered from a template and delivered immediately via your configured messenger. This guide explains the complete implementation—from preparing templates to SMTP transmission—based on the actual source code in the `knadh/listmonk` repository.

## Prerequisites: Create a Transactional Template

Before calling the API, you need a transactional template stored in the database with `type = "tx"`. Create this via the Listmonk UI or the `POST /api/templates` endpoint. The template body uses Go’s `text/template` syntax:

```text
Hello {{ .Subscriber.Name }},

Your order #{{ .Data.order_id }} has been shipped.

```

If the template defines a subject, Listmonk uses it unless you override it in the API request. The template is cached at startup and retrieved via `a.manager.GetTpl()` in [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go) during request processing.

## Configure API Authentication and Permissions

Listmonk authenticates transactional requests using **API keys** configured as “API users.”

1. Navigate to *Settings → Users* and create an API user.
2. Grant the **`tx:send`** permission (defined as the constant `PermTxSend = "tx:send"` in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go)).
3. Pass the key in the `Authorization` header using the format parsed by `parseAuthHeader` in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go):

```

Authorization: ApiKey <api_key>:<access_token>

```

The route `POST /api/tx` is registered in [`cmd/handlers.go`](https://github.com/knadh/listmonk/blob/main/cmd/handlers.go) with the permission middleware `pm(a.SendTxMessage, "tx:send")`, rejecting requests that lack this scope.

## Construct the Request Payload

Listmonk accepts two content types: `application/json` for simple sends, and `multipart/form-data` when including attachments.

### JSON Payload Structure

Send a `POST` request to `/api/tx` with the following fields:

```json
{
  "template_id": 12,
  "subscriber_emails": ["alice@example.com"],
  "data": {
    "order_id": "12345"
  },
  "from_email": "no-reply@mydomain.com",
  "subject": "Your order shipped",
  "content_type": "html",
  "messenger": "email"
}

```

**Key parameters:**
- **`template_id`** (required): Integer ID of the TX template.
- **`subscriber_emails`** or **`subscriber_ids`**: Exactly one must be present. The handler validates this via `validateTxMessage` in [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go).
- **`subscriber_mode`**: `"default"`, `"fallback"`, or `"external"`—controls how subscriber data is resolved.
- **`data`**: Arbitrary map accessible in templates via `{{ .Data.<key> }}`.
- **`from_email`**: Overrides the global default sender.
- **`subject`**: Overrides the template’s subject line.
- **`content_type`**: `"html"` (default) or `"plain"`.
- **`headers`**: Optional map of additional SMTP headers (e.g., `{"Reply-To": ["support@domain.com"]}`).

### Multipart Form Data for Attachments

To include attachments, send `multipart/form-data`:
- **`data`**: A string containing the JSON payload described above.
- **`file`**: One or more file parts. The handler reads these in [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go), constructs `models.Attachment` objects using `manager.MakeAttachmentHeader`, and appends them to `TxMessage.Attachments`.

## Practical Implementation Examples

### cURL (JSON Only)

```bash
curl -X POST https://listmonk.example.com/api/tx \
  -H "Authorization: ApiKey $API_KEY:$TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "template_id": 12,
        "subscriber_emails": ["bob@example.com"],
        "data": {"order_id":"9876"},
        "subject": "Your order #9876 shipped",
        "from_email": "orders@example.com"
      }'

```

### cURL with File Attachments

```bash
curl -X POST https://listmonk.example.com/api/tx \
  -H "Authorization: ApiKey $API_KEY:$TOKEN" \
  -F "data={\"template_id\":12,\"subscriber_emails\":[\"bob@example.com\"],\"data\":{\"order_id\":\"9876\"}}" \
  -F "file=@/path/to/invoice.pdf;type=application/pdf"

```

### Python Requests

```python
import requests
import json

API_URL = "https://listmonk.example.com/api/tx"
API_KEY = "myapikey"
TOKEN = "mytoken"

payload = {
    "template_id": 12,
    "subscriber_emails": ["carol@example.com"],
    "data": {"order_id": "1122"},
    "subject": "Your order #1122 shipped"
}

headers = {
    "Authorization": f"ApiKey {API_KEY}:{TOKEN}",
    "Content-Type": "application/json"
}

response = requests.post(API_URL, headers=headers, data=json.dumps(payload))
print(response.status_code, response.json())

```

## Internal Architecture: From API Call to SMTP Delivery

Understanding the internal flow helps debug delivery issues:

1. **Route Handling**: [`cmd/handlers.go`](https://github.com/knadh/listmonk/blob/main/cmd/handlers.go) registers `POST /api/tx` with the auth middleware requiring `tx:send`.
2. **Request Parsing**: `SendTxMessage` in [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go) parses JSON or multipart input and sanitizes emails via `a.importer.SanitizeEmail`.
3. **Validation**: `validateTxMessage` ensures only `subscriber_emails` or `subscriber_ids` is used, and validates subscriber modes.
4. **Template Resolution**: `a.manager.GetTpl(m.TemplateID)` fetches the cached template.
5. **Rendering**: `TxMessage.Render` in [`models/messages.go`](https://github.com/knadh/listmonk/blob/main/models/messages.go) executes Go templates against the data structure `{Subscriber, Tx}`, producing the final body and subject.
6. **Message Construction**: The handler builds a `models.Message` with `From`, `To`, rendered content, headers, and attachments.
7. **Messenger Push**: `a.manager.PushMessage(msg)` routes to the appropriate messenger. For email, `Emailer.Push` in [`internal/messenger/email/email.go`](https://github.com/knadh/listmonk/blob/main/internal/messenger/email/email.go) builds an `smtppool.Email`, attaches headers like `Return-Path`, and calls `srv.pool.Send(em)`.

Each stage returns specific HTTP status codes—`400` for validation errors, `403` for permission failures, and `200` on successful queueing.

## Summary

- **Create a TX template** with `type = "tx"` before sending.
- **Grant `tx:send` permission** to API keys in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go).
- **POST to `/api/tx`** with JSON for text/html or multipart for attachments.
- **Reference specific files** like [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go) for the entry point and [`internal/messenger/email/email.go`](https://github.com/knadh/listmonk/blob/main/internal/messenger/email/email.go) for SMTP logic.
- **Use `subscriber_emails` or `subscriber_ids`**, but never both, as enforced by `validateTxMessage`.

## Frequently Asked Questions

### What is the difference between a campaign and a transactional email in Listmonk?

**Campaigns** are bulk messages sent to lists and managed through the campaign scheduler. **Transactional emails** are immediate, single-recipient sends triggered via the API, rendered from TX templates, and pushed directly to the messenger without campaign overhead.

### Can I send transactional emails to addresses that are not subscribed to any list?

Yes. Set `subscriber_mode` to `"external"` in your payload. This bypasses the standard subscriber lookup, allowing you to send to any valid email address even if it does not exist in your Listmonk database.

### How are attachments processed when sending via the API?

When using `multipart/form-data`, the handler in [`cmd/tx.go`](https://github.com/knadh/listmonk/blob/main/cmd/tx.go) extracts file parts, reads the bytes, and constructs `models.Attachment` objects using `manager.MakeAttachmentHeader`. These attachments are appended to the message and encoded by the `Emailer.Push` method in [`internal/messenger/email/email.go`](https://github.com/knadh/listmonk/blob/main/internal/messenger/email/email.go) before SMTP transmission.

### Why is my transactional email request returning a 403 error?

A `403 Forbidden` indicates your API key lacks the required permission. Verify that the key includes the `tx:send` scope, defined as the constant `PermTxSend` in [`internal/auth/auth.go`](https://github.com/knadh/listmonk/blob/main/internal/auth/auth.go). The middleware registered in [`cmd/handlers.go`](https://github.com/knadh/listmonk/blob/main/cmd/handlers.go) strictly enforces this permission for the `/api/tx` endpoint.