Categories group transactional emails for analytics, so you can report on sends by type (order confirmation, password reset, and so on). Register a category once, then reference it by its integer ID in the category field of a send.
Endpoint and headers
POST https://email.useinsider.com/mail/v2/categories
Header | Value | Purpose |
|---|---|---|
X-INS-AUTH-KEY | API token, format INS.{id}.{secret} | Authentication |
Content-Type | application/json | Request format |
Body parameters
Below are the body parameters for this request.
Parameter | Description | Data type | Required |
|---|---|---|---|
name | Lowercase, matching ^[a-z0-9][a-z0-9_-]{0,63}$. Unique per partner. Immutable. | String | Yes |
description | Free-form, up to 255 characters. Immutable. | String | No |
Body example
Below is a sample body for this request
{
"name": "order_confirmation",
"description": "Post-purchase confirmations"
}Code samples
These samples are provided as is, as a starting point for your integration. Insider One does not guarantee that they work in every environment, so test them in your own environment before you use them in production.
Each sample creates the order_confirmation category and prints the ID to use in a send.
Set your API key in the INSIDER_API_KEY environment variable.
Copy the sample for your language into a file named create_category.py or create_category.mjs.
Run the file. The sample prints Category ID: 3, with your category's ID.
Python
Requires Python 3.8 or later and the requests package (pip install requests). Run python create_category.py.
"""Create an analytics category with POST /mail/v2/categories.
Sample code, provided as is. Test it in your own environment before you use it in production.
Requires Python 3.8+ and the requests package: pip install requests
"""
import os
import time
import requests
URL = "https://email.useinsider.com/mail/v2/categories"
# OAuth 2.0: call https://gw.useinsider.com/api/em/v2/tx/categories with "Authorization: Bearer {token}" instead.
HEADERS = {"X-INS-AUTH-KEY": os.environ["INSIDER_API_KEY"], "Content-Type": "application/json"}
def call(method, url, payload=None, retries=3):
"""Send the request. On 429 or 5xx, wait and retry up to `retries` times."""
for attempt in range(retries + 1):
response = requests.request(method, url, json=payload, headers=HEADERS, timeout=30)
if (response.status_code != 429 and response.status_code < 500) or attempt == retries:
return response
wait = float(response.headers.get("Retry-After", 2 ** attempt)) # 1, 2, 4 s without Retry-After
print(f"HTTP {response.status_code}: retrying in {wait:.0f}s")
time.sleep(wait)
payload = {"name": "order_confirmation", "description": "Post-purchase confirmations"}
response = call("POST", URL, payload)
if response.status_code == 201:
print("Category ID:", response.json()["id"])
elif response.status_code == 409: # The name exists. Categories cannot be renamed or deleted, so reuse it.
print("Category ID:", response.json()["existing_id"])
else:
print(f"Create failed ({response.status_code}): {response.text[:300]}")Node.js
Requires Node.js 18 or later. No packages are needed. Run node create_category.mjs.
// Create an analytics category with POST /mail/v2/categories.
// Sample code, provided as is. Test it in your own environment before you use it in production.
// Requires Node.js 18+ (built-in fetch). No dependencies.
const URL = "https://email.useinsider.com/mail/v2/categories";
// OAuth 2.0: call https://gw.useinsider.com/api/em/v2/tx/categories with "Authorization: Bearer {token}" instead.
const HEADERS = { "X-INS-AUTH-KEY": process.env.INSIDER_API_KEY, "Content-Type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Send the request. On 429 or 5xx, wait and retry up to `retries` times.
async function call(method, url, payload, retries = 3) {
for (let attempt = 0; ; attempt++) {
const response = await fetch(url, {
method,
headers: HEADERS,
body: payload === undefined ? undefined : JSON.stringify(payload),
signal: AbortSignal.timeout(30000),
});
if ((response.status !== 429 && response.status < 500) || attempt === retries) return response;
const wait = Number(response.headers.get("Retry-After") ?? 2 ** attempt); // 1, 2, 4 s without Retry-After
console.warn(`HTTP ${response.status}: retrying in ${wait}s`);
await sleep(wait * 1000);
}
}
const payload = { name: "order_confirmation", description: "Post-purchase confirmations" };
const response = await call("POST", URL, payload);
if (response.status === 201) {
console.log("Category ID:", (await response.json()).id);
} else if (response.status === 409) { // The name exists. Categories cannot be renamed or deleted, so reuse it.
console.log("Category ID:", (await response.json()).existing_id);
} else {
console.error(`Create failed (${response.status}): ${(await response.text()).slice(0, 300)}`);
}Sample response
A successful create returns 201 with the new category and its ID.
{
"id": 3,
"name": "order_confirmation",
"description": "Post-purchase confirmations",
"created_at": "2026-06-29T10:00:00Z"
}Keep in mind
Categories are append-only. Once an ID is issued, it is referenced by historical logs and analytics, so the name and description cannot be changed, and it cannot be deleted. To retire a category, simply stop using it.
A partner can hold up to 100 categories.
For the reporting view, refer to Transactional Email Analytics. For error codes such as category_exists and category_limit_reached, refer to the Error Codes.
Suggested Next
See POST /mail/v2/send to include the new category ID in the category field of your send request. Use the integer id returned by this endpoint, not the category name.
After sending emails with this category, see Transactional Email Analytics to filter your reports and review performance by email type.