# Agent setup Source: https://docs.minimo.it/agent-setup Point your AI agent at this page and it will connect itself to Minimo. This page doubles as an **executable prompt**. Copy the block below into your AI coding agent (Claude Code, Cursor, Codex, Copilot, …) and it will connect itself to the Minimo MCP server. The raw instructions are also available at [`https://docs.minimo.it/agent-setup.md`](https://docs.minimo.it/agent-setup.md). ## Copy this into your agent ```text theme={null} Fetch and execute the instructions at https://docs.minimo.it/agent-setup.md to connect me to Minimo. ``` That's it. Your agent reads the steps below, adds the Minimo MCP server with the right command for your tool, and confirms when it's done. *** ## Instructions for the agent These are official instructions from Minimo to connect an AI agent to a Minimo workspace via the **Model Context Protocol (MCP)**. Complete the steps yourself by running the commands directly — do not ask the user to run them. The Minimo MCP endpoint is `https://api.minimo.it/mcp` (Streamable HTTP). Authentication is OAuth ("Sign in with Minimo"): on first tool use a browser window opens and the user clicks **Authorize** — there is no key to copy. For headless or CI environments, use an `mn-…` API key with the `mcp:invoke` permission as a Bearer header instead. ### Claude Code ```bash theme={null} claude mcp add --transport http minimo https://api.minimo.it/mcp ``` ### Cursor — `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) Add under `"mcpServers"`: ```json theme={null} "minimo": { "url": "https://api.minimo.it/mcp" } ``` ### Codex ```bash theme={null} codex mcp add minimo --url https://api.minimo.it/mcp ``` ### GitHub Copilot (VS Code) — `.vscode/mcp.json`, Windsurf, and other agents Add under `"mcpServers"` (Windsurf uses `serverUrl` instead of `url`): ```json theme={null} "minimo": { "url": "https://api.minimo.it/mcp" } ``` ### Headless / CI (no browser to sign in) Pass an API key as a Bearer header. Get one from **Settings → Mo & MCP** (the "Use Mo everywhere" card) or **Developer → API keys** with the **MCP (AI agent)** permission. ```bash theme={null} claude mcp add --transport http minimo https://api.minimo.it/mcp \ --header "Authorization: Bearer mn-YOUR-API-KEY" ``` ### Verify After connecting, confirm it works by asking the agent a read-only question: ```text theme={null} Using Minimo: which workspace am I connected to, and how many contacts do I have? ``` If the agent returns a workspace name and a contact count, the connection is live. Then tell the user: ``` ✓ Minimo connected. Your agent can now read and act on your workspace — contacts, segments, email & WhatsApp templates, campaigns and automations — through Mo's tools. ``` These instructions are published at `https://docs.minimo.it/agent-setup.md` so you can re-verify their authenticity at any time. For the human-readable walkthrough per client, see [Connect your agent (MCP)](/guides/connect-your-agent). # Delete Contact Source: https://docs.minimo.it/api-reference/core-data/contacts/delete delete /public/v1/contacts/{id} Soft-delete a contact by ID This performs a **soft delete** — the contact is marked as deleted but not permanently removed from the database. Soft-deleted contacts can be restored by calling the [upsert](/api-reference/core-data/contacts/upsert) or [update by ID](/api-reference/core-data/contacts/update-by-id) endpoint. ## Common Errors | Error | Cause | Solution | | -------------- | ----------------------- | -------------------------------------- | | `not_found` | No contact with this ID | Verify the contact ID | | `unauthorized` | Invalid API key | Verify API key in Authorization header | ## Related Endpoints * **[Create or Update Contact](/api-reference/core-data/contacts/upsert)**: Create a new contact or restore a deleted one * **[Get Contact by Email](/api-reference/core-data/contacts/get-by-email)**: Look up a contact by email * **[Update Contact by ID](/api-reference/core-data/contacts/update-by-id)**: Update a specific contact # Get Contact by Email Source: https://docs.minimo.it/api-reference/core-data/contacts/get-by-email get /public/v1/contacts/by-email/{email} Retrieve a contact and all its data by email address Use this endpoint to look up a contact's ID, then use **[Update by ID](/api-reference/core-data/contacts/update-by-id)** to modify it — for example, to change the contact's email address. ## Use Cases * **Look up contact ID**: Find a contact's internal ID to use with other endpoints * **Check if contact exists**: Verify whether an email is already in your contact list * **Read contact data**: Retrieve full contact details including custom fields ## Response Returns the full contact object: ```json theme={null} { "data": { "id": 42, "email": "customer@example.com", "phone": "+393391234567", "status": "active", "source": "API", "custom_fields": { "company": "Acme Corp", "plan": "enterprise" }, "company": 1, "deleted": false, "external_id": null, "external_connection_id": null } } ``` Soft-deleted contacts are not returned — a `404` is returned instead. ## Common Errors | Error | Cause | Solution | | -------------- | -------------------------- | -------------------------------------- | | `bad_request` | Invalid email format | Ensure the email address is valid | | `not_found` | No contact with this email | Check the email address | | `unauthorized` | Invalid API key | Verify API key in Authorization header | ## Example: Look Up and Update a Contact ```javascript theme={null} const API_URL = 'https://api.minimo.it/public/v1/contacts'; const headers = { Authorization: 'Bearer mn-YOUR_CLIENT_ID-YOUR_API_KEY', 'Content-Type': 'application/json', }; // Step 1: Get the contact by email const email = 'customer@example.com'; const contact = await fetch(`${API_URL}/by-email/${encodeURIComponent(email)}`, { headers }).then((res) => res.json()); // Step 2: Update the contact by ID (e.g., change email) await fetch(`${API_URL}/${contact.data.id}`, { method: 'PUT', headers, body: JSON.stringify({ email: 'new-email@example.com' }), }); ``` ## Related Endpoints * **[Create or Update Contact](/api-reference/core-data/contacts/upsert)**: Upsert a contact by email or phone * **[Update Contact by ID](/api-reference/core-data/contacts/update-by-id)**: Update a specific contact * **[Delete Contact](/api-reference/core-data/contacts/delete)**: Soft-delete a contact # Update Contact by ID Source: https://docs.minimo.it/api-reference/core-data/contacts/update-by-id put /public/v1/contacts/{id} Update an existing contact using their ID Unlike the [upsert endpoint](/api-reference/core-data/contacts/upsert) which matches by email or phone, this endpoint updates a specific contact by their ID. Use this when you need a deterministic update by ID — especially when changing a contact's email address. ## When to Use This | Scenario | Use this endpoint | Use upsert instead | | ---------------------------- | ----------------- | --------------------------------------------------------------------------- | | Change a contact's email | Yes | Only if phone number is also provided (upsert falls back to phone matching) | | Update a known contact by ID | Yes | Either works | | Create a new contact | No (returns 404) | Yes | | Update by email/phone match | No | Yes | ## How It Works * **Only provided fields are updated** — omitted fields are preserved * **Custom fields are deep merged** — existing custom fields not included in the request are kept * **Automations are triggered** — field change detection and automation triggers work the same as upsert * **Soft-deleted contacts are restored** — if the contact was deleted, it will be restored with the provided data ## Marketing Consent Update channel-specific marketing consent for a contact: ```json theme={null} { "marketingConsent": { "email": true, "whatsapp": false } } ``` | Field | Type | Description | | ---------- | ------- | ------------------------------------------------------- | | `email` | boolean | Email marketing opt-in (`true`) or opt-out (`false`) | | `whatsapp` | boolean | WhatsApp marketing opt-in (`true`) or opt-out (`false`) | Both fields are optional. Omitting a channel leaves its current consent status unchanged. Setting `true` opts the contact in, `false` opts them out. This endpoint does **not** create new contacts. If no contact exists with the given ID, a `404` error is returned. ## Example: Change a Contact's Email ```bash theme={null} curl --request GET \ --url 'https://api.minimo.it/public/v1/contacts/by-email/old@example.com' \ --header 'Authorization: Bearer mn-YOUR_CLIENT_ID-YOUR_API_KEY' ``` Response: `{ "data": { "id": 42, "email": "old@example.com", ... } }` ```bash theme={null} curl --request PUT \ --url 'https://api.minimo.it/public/v1/contacts/42' \ --header 'Authorization: Bearer mn-YOUR_CLIENT_ID-YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "email": "new@example.com" }' ``` ## Common Errors | Error | Cause | Solution | | -------------- | ----------------------- | -------------------------------------- | | `not_found` | No contact with this ID | Verify the contact ID | | `unauthorized` | Invalid API key | Verify API key in Authorization header | ## Related Endpoints * **[Get Contact by Email](/api-reference/core-data/contacts/get-by-email)**: Look up a contact's ID by email * **[Create or Update Contact](/api-reference/core-data/contacts/upsert)**: Upsert by email or phone * **[Delete Contact](/api-reference/core-data/contacts/delete)**: Soft-delete a contact # Create or Update Contact Source: https://docs.minimo.it/api-reference/core-data/contacts/upsert post /public/v1/contacts Insert a new contact or update an existing one based on email or phone This endpoint performs an **upsert**: it first tries to match by email, then falls back to phone if no email match is found. If no match is found, a new contact is created. ## How Matching Works The endpoint matches contacts in this order: If `email` is provided, searches for an existing contact with the same email in your company. If no email match is found and `phone` is provided, searches by phone number. If no match is found, creates a new contact. Need to update a contact's email? Prefer **[Update by ID](/api-reference/core-data/contacts/update-by-id)** for a deterministic update. Upsert only avoids creating a new record if a phone number is also provided and matches an existing contact. ## Custom Fields Store additional data using the `customFields` object. Fields are flexible key-value pairs: ```json theme={null} { "email": "customer@example.com", "customFields": { "company": "Acme Corp", "plan": "enterprise", "signup_date": "2025-11-13" } } ``` When updating an existing contact, custom fields are **deep merged** — only the fields you include are changed, existing fields are preserved. ## Marketing Consent Control channel-specific marketing consent when creating or updating a contact: ```json theme={null} { "email": "customer@example.com", "marketingConsent": { "email": true, "whatsapp": false } } ``` | Field | Type | Description | | ---------- | ------- | ------------------------------------------------------- | | `email` | boolean | Email marketing opt-in (`true`) or opt-out (`false`) | | `whatsapp` | boolean | WhatsApp marketing opt-in (`true`) or opt-out (`false`) | Both fields are optional. Omitting a channel leaves its current consent status unchanged. Setting `true` opts the contact in, `false` opts them out. ## Phone Numbers For WhatsApp messaging, include a phone number in **E.164 format**: ```json theme={null} { "email": "customer@example.com", "phone": "+393391234567" } ``` **Format requirements**: * Include country code (e.g., `+39` for Italy, `+1` for US) * No spaces, dashes, or parentheses * Example: `+12025551234` (US), `+393391234567` (Italy) Invalid phone numbers will cause WhatsApp message sending to fail. ## Common Errors | Error | Cause | Solution | | ---------------------- | ------------------------- | -------------------------------------- | | `validation_error` | Invalid email format | Check email address format | | `invalid_phone_number` | Phone not in E.164 format | Add country code, remove spaces | | `rate_limit_exceeded` | Too many requests | Implement rate limiting, use batching | | `unauthorized` | Invalid API key | Verify API key in Authorization header | ## Related Endpoints * **[Get Contact by Email](/api-reference/core-data/contacts/get-by-email)**: Look up a contact by email * **[Update Contact by ID](/api-reference/core-data/contacts/update-by-id)**: Update a specific contact by ID * **[Delete Contact](/api-reference/core-data/contacts/delete)**: Soft-delete a contact * **[Custom Fields](/api-reference/core-data/custom-fields/list)**: Manage custom field definitions # Create Custom Field Source: https://docs.minimo.it/api-reference/core-data/custom-fields/create post /public/v1/custom-fields Create a new custom field to store additional contact data Custom fields allow you to define and organize additional attributes for your contacts beyond the standard fields (email, phone, name). ## Use Cases * **Lead qualification**: Store lead score, source, or stage * **Segmentation**: Categorize contacts by industry, plan, or region * **Personalization**: Store preferences for personalized messaging * **Integration sync**: Map external CRM fields to Minimo ## Field Types | Type | Description | | ---------- | ----------------------------- | | `text` | Free text input | | `number` | Numeric value | | `boolean` | True/false | | `date` | Date value | | `datetime` | Date and time | | `select` | Single selection from options | | `json` | JSON object | ## Best Practices Use `SNAKE_CASE` for keys (e.g., `COMPANY_SIZE`, `LEAD_SOURCE`). Keys are automatically uppercased. Group related fields using categories like "Company Info", "Lead Data", "Preferences" for better organization. Choose the right type for your data. Use `number` for numeric values, `date` for dates, and `select` for predefined options. ## Common Errors | Error | Cause | Solution | | -------------------- | ------------------------ | ---------------------------------- | | `key already exists` | Duplicate key in account | Use a different key name | | `validation_error` | Missing required fields | Include key, displayName, and type | | `unauthorized` | Invalid API key | Check Authorization header | ## Code Examples ```javascript theme={null} const response = await fetch('https://api.minimo.it/public/v1/custom-fields', { method: 'POST', headers: { 'Authorization': 'Bearer mn-YOUR_CLIENT_ID-YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'LEAD_SOURCE', displayName: 'Lead Source', type: 'select', category: 'Lead Data' }) }); const { data } = await response.json(); console.log(data); ``` ```python theme={null} import requests headers = { 'Authorization': 'Bearer mn-YOUR_CLIENT_ID-YOUR_API_KEY', 'Content-Type': 'application/json' } response = requests.post( 'https://api.minimo.it/public/v1/custom-fields', headers=headers, json={ 'key': 'LEAD_SOURCE', 'displayName': 'Lead Source', 'type': 'select', 'category': 'Lead Data' } ) print(response.json()) ``` ## Related Endpoints * **[List Custom Fields](/api-reference/core-data/custom-fields/list)**: Retrieve all custom fields * **[Create or Update Contact](/api-reference/core-data/contacts/upsert)**: Create and update contacts with custom field values # List Custom Fields Source: https://docs.minimo.it/api-reference/core-data/custom-fields/list get /public/v1/custom-fields Retrieve all custom fields defined for your account Use the `groupBy=category` query parameter to get custom fields organized by category for easier management. ## Use Cases * **Display field options**: Show available custom fields in your application UI * **Integration mapping**: Map external CRM fields to Minimo custom fields * **Data validation**: Check which fields exist before creating contacts ## Group by Category Get custom fields organized by category by adding the `groupBy=category` query parameter: ```bash theme={null} curl --request GET \ --url 'https://api.minimo.it/public/v1/custom-fields?groupBy=category' \ --header 'Authorization: Bearer mn-YOUR_CLIENT_ID-YOUR_API_KEY' ``` **Grouped Response:** ```json theme={null} { "data": { "groups": [ { "category": "Company Info", "fields": [ { "id": 1, "key": "COMPANY_SIZE", "displayName": "Company Size", "type": "text" }, { "id": 2, "key": "INDUSTRY", "displayName": "Industry", "type": "select" } ] }, { "category": "uncategorized", "fields": [{ "id": 3, "key": "NOTES", "displayName": "Notes", "type": "text" }] } ], "total": 3 } } ``` Fields without a category are automatically grouped under `uncategorized`. ## Field Types | Type | Description | | ---------- | ----------------------------- | | `text` | Free text input | | `number` | Numeric value | | `boolean` | True/false | | `date` | Date value | | `datetime` | Date and time | | `select` | Single selection from options | | `json` | JSON object | ## Common Errors | Error | Cause | Solution | | ------------------ | --------------------- | ------------------------------------ | | `unauthorized` | Invalid API key | Check Authorization header format | | `invalid_group_by` | Invalid groupBy value | Use `category` or omit the parameter | ## Related Endpoints * **[Create Custom Field](/api-reference/core-data/custom-fields/create)**: Create a new custom field * **[Create or Update Contact](/api-reference/core-data/contacts/upsert)**: Create and update contacts with custom field values # Legacy API (Deprecated) Source: https://docs.minimo.it/api-reference/legacy/introduction Deprecated endpoints maintained for backward compatibility **This section contains deprecated endpoints that will be removed on June 1, 2026.** Please migrate to the new endpoints in the [Messaging Channels](/api-reference/messaging-channels/email/send-template) section. ## Why These Endpoints Are Deprecated The original "Transactionals" API had several limitations: * ❌ Inconsistent naming and structure * ❌ Limited template management capabilities * ❌ No support for WhatsApp and other channels * ❌ Unclear separation between channels * ❌ Missing analytics and tracking features The new **Messaging Channels** API addresses all these issues with: * ✅ Consistent structure across all channels * ✅ Better template management * ✅ Multi-channel support (Email, WhatsApp, SMS coming soon) * ✅ Enhanced analytics and stats * ✅ Improved error handling and validation ## Migration Guide ### Email Transactionals → Email Templates **Old endpoint** (deprecated): ```bash theme={null} POST /api/transactionals ``` **New endpoint**: ```bash theme={null} POST /public/v1/templates/email/send ``` ### Request Structure Comparison ```json Old (Deprecated) theme={null} { "uid": "unique_template_id", "recipient": "customer@example.com", "customFields": { "name": "John Doe", "orderNumber": "12345" } } ``` ```json New (Recommended) theme={null} { "uid": "tmpl_abc123", "recipient": "customer@example.com", "customFields": { "name": "John Doe", "orderNumber": "12345" } } ``` **Key changes**: * Template IDs now use `tmpl_` prefix for clarity * Response includes more metadata ### WhatsApp Migration **Old endpoint** (deprecated): ```bash theme={null} POST /api/transactionals/whatsapp/send GET /api/transactionals/whatsapp/templates ``` **New endpoints** (on `api.minimo.it`): ```bash theme={null} POST https://api.minimo.it/public/v1/templates/whatsapp/send GET https://api.minimo.it/public/v1/templates/whatsapp ``` The structure is largely the same, but with improved error messages and validation. Note the new endpoints are served from the `api.minimo.it` domain. ## Migration Timeline | Date | Milestone | | ----------------- | --------------------------------------------------- | | **November 2025** | New API released, legacy API deprecated | | **December 2025** | Migration guide published | | **March 2026** | Legacy API enters maintenance mode (bug fixes only) | | **May 2026** | Final migration warnings sent to all users | | **June 1, 2026** | **Legacy API removed** | You have until **June 1, 2026** to migrate. After this date, the legacy endpoints will return `410 Gone` errors. ## How to Migrate Identify where you're using the old endpoints: ```bash theme={null} # Search your codebase grep -r "/api/transactionals" . ``` Replace old endpoints with new ones: * `/api/transactionals` → `/public/v1/templates/email/send` * `/api/transactionals/whatsapp/send` → `/public/v1/templates/whatsapp/send` * `/api/transactionals/whatsapp/templates` → `/public/v1/templates/whatsapp` Verify template IDs in your request payloads: ```javascript theme={null} // Before { uid: "abc123", recipient: "user@example.com" } // After (templated ID prefixed with tmpl_) { uid: "tmpl_abc123", recipient: "user@example.com" } ``` Verify the new endpoints work as expected in your staging environment before deploying to production. Once tested, deploy your changes to production. Check logs to ensure no errors and that messages are being delivered successfully. ## Get Migration Help Need assistance migrating? * **Email support**: [info@minimo.it](mailto:info@minimo.it) * **Documentation**: [New Messaging Channels API](/api-reference/messaging-channels/email/send-template) * **Dashboard**: Check your API usage at [app.minimo.it/account](https://app.minimo.it/account) We're here to help! If you encounter any issues during migration, reach out to our support team. ## Still Using Legacy Endpoints? If you're still using the deprecated endpoints below, you'll see deprecation warnings in API responses: ```json theme={null} { "data": { /* your response */ }, "warning": { "message": "This endpoint is deprecated and will be removed on June 1, 2026", "deprecationDate": "2026-06-01", "migrateTo": "/public/v1/templates/email/send", "docsUrl": "https://docs.minimo.it/api-reference/messaging-channels/email/send-template" } } ``` Use these warnings to track which integrations need updating. # List Email Transactionals (Deprecated) Source: https://docs.minimo.it/api-reference/legacy/list-email-transactionals-deprecated Retrieve list of email transactionals - deprecated endpoint **This endpoint is deprecated and will be removed on June 1, 2026.** Please migrate to the new [List Email Templates](/api-reference/messaging-channels/email/list-templates) endpoint. ## Why This Is Deprecated This endpoint has been replaced with a clearer, more consistent API structure. ### Use the New Endpoint Instead **New endpoint**: `GET /public/v1/templates/email` [View new endpoint documentation →](/api-reference/messaging-channels/email/list-templates) ## Migration The new endpoint provides more information and better filtering: ### Old Response (This Endpoint) ```json theme={null} { "templates": [ { "uid": "abc123", "name": "Order Confirmation" } ] } ``` ### New Response (Recommended) ```json theme={null} { "data": [ { "id": "tmpl_abc123", "name": "Order Confirmation", "subject": "Your order is confirmed", "status": "active", "category": "transactional", "createdAt": "2025-10-01T10:00:00Z", "variables": ["orderNumber", "customerName"] } ] } ``` **Benefits of new endpoint**: * ✅ More template metadata * ✅ Status and category information * ✅ List of template variables * ✅ Timestamps for auditing * ✅ Consistent naming (`id` instead of `uid`) ## Migration Deadline This endpoint will be removed on **June 1, 2026**. [Read the full migration guide →](/api-reference/legacy/introduction) ## Need Help? * [Migration Guide](/api-reference/legacy/introduction) * [New Email Templates API](/api-reference/messaging-channels/email/list-templates) * [Contact Support](mailto:info@minimo.it) # Send Email (Deprecated) Source: https://docs.minimo.it/api-reference/legacy/send-email-deprecated post /api/transactionals **This endpoint is deprecated and will be removed on June 1, 2026.** Please migrate to the new [Send Email Template](/api-reference/messaging-channels/email/send-template) endpoint. ## Why This Is Deprecated This endpoint has been replaced with a more flexible and consistent API under **Messaging Channels**. ### What's Wrong With This Endpoint? * Inconsistent with other channel APIs * Limited error handling * No support for advanced template features ### Use the New Endpoint Instead **New endpoint**: `POST /public/v1/templates/email/send` [View new endpoint documentation →](/api-reference/messaging-channels/email/send-template) ## Migration ### Old Request (This Endpoint) ```json theme={null} { "uid": "unique_template_id", "recipient": "customer@example.com", "customFields": { "name": "John Doe", "orderNumber": "12345" } } ``` ### New Request (Recommended) ```json theme={null} { "uid": "tmpl_abc123", "recipient": "customer@example.com", "customFields": { "name": "John Doe", "orderNumber": "12345" } } ``` **Changes needed**: 1. Update endpoint URL: `/api/transactionals` → `/public/v1/templates/email/send` 2. Update template IDs in your code (now prefixed with `tmpl_`) ## Migration Deadline | Date | Status | | ------------- | ---------------------------------- | | November 2025 | Deprecated (still works) | | June 1, 2026 | **Removed** (will return 410 Gone) | You have until **June 1, 2026** to migrate. Plan your migration now to avoid service disruption. ## Need Help? * [Migration Guide](/api-reference/legacy/introduction) * [New Email API Docs](/api-reference/messaging-channels/email/send-template) * [Contact Support](mailto:info@minimo.it) # Get One-Shot Email Statistics Source: https://docs.minimo.it/api-reference/messaging-channels/email/html-stats get /public/v1/emails/{id}/stats Poll delivery, open, and click metrics for a single HTML email sent via the one-shot endpoint ## Overview After dispatching an email through [`POST /public/v1/emails`](/api-reference/messaging-channels/email/send-html), use the returned `id` (or the full `statsUrl`) to read back delivery and engagement metrics for that **single** delivery. Unlike the template-stats endpoint, these numbers refer to **one recipient and one HTML payload** — not aggregated counters across a campaign. The endpoint is scoped to your company. IDs that belong to a different company return `404`, never another tenant's data. ## Request ```bash theme={null} curl --request GET \ --url https://api.minimo.it/public/v1/emails/DMPNEF8/stats \ --header 'Authorization: Bearer mn-{apiClientId}-{secret}' ``` ## Response Example ```json theme={null} { "data": { "id": "DMPNEF8", "status": "sent", "to": "customer@example.com", "subject": "Your receipt", "sentAt": "2026-05-19T10:30:00Z", "opened": true, "firstOpenAt": "2026-05-19T10:32:14Z", "clickCount": 2, "firstClickAt": "2026-05-19T10:33:05Z", "failureReason": null } } ``` ## Field Reference | Field | Type | Description | | --------------- | --------------------------------- | --------------------------------------------------------------------------- | | `id` | `string` | Echo of the opaque sqid id returned by the POST endpoint. | | `status` | `"pending" \| "sent" \| "failed"` | Lifecycle state of the delivery. | | `to` | `string` | Recipient address as it was sent. | | `subject` | `string` | Subject line as it was sent. | | `sentAt` | `date-time` | When the delivery row was created (queue-in time, not provider acceptance). | | `opened` | `boolean` | `true` once the tracking pixel has been fetched at least once. | | `firstOpenAt` | `date-time \| null` | Timestamp of the first open; `null` until the pixel is fetched. | | `clickCount` | `integer` | Total number of clicks across all tracked links in the email. | | `firstClickAt` | `date-time \| null` | Timestamp of the earliest tracked click; `null` until the first click. | | `failureReason` | `string \| null` | Populated when `status = "failed"` — the provider's rejection reason. | ## How the Counters Are Updated * **Open**: triggered the first time the recipient's email client fetches the embedded `` open-pixel. Image proxies (Gmail, Apple Mail Privacy Protection) trigger this immediately on arrival, which inflates open rates compared to pre-2021 baselines. * **Click**: every fetch of a rewritten `/api/click/...` URL appends a row to the click ledger. `clickCount` is the cardinality of that ledger; `firstClickAt` is its minimum timestamp. Both signals are **per delivery**: opens and clicks tied to a different `id` do not affect this row. ## Use Cases After sending a sign-in email, poll the stats endpoint to surface "Email opened by recipient" in your internal dashboard. ```javascript theme={null} const { data: stats } = await fetch(`https://api.minimo.it/public/v1/emails/${id}/stats`, { headers: { Authorization: `Bearer ${apiKey}` }, }).then((r) => r.json()); if (stats.opened) { console.log(`Magic link opened at ${stats.firstOpenAt}`); } ``` When `status === "failed"`, persist `failureReason` against the originating business object so support can react. ```javascript theme={null} const { data: stats } = await getEmailStats(id); if (stats.status === 'failed') { await logDeliveryFailure({ id, reason: stats.failureReason }); } ``` The first click timestamp lets you measure end-to-end latency from "we sent it" to "user acted." ```javascript theme={null} const { data: stats } = await getEmailStats(id); if (stats.firstClickAt) { const sentAt = new Date(stats.sentAt); const clickedAt = new Date(stats.firstClickAt); const minutesToFirstClick = (clickedAt - sentAt) / 60000; track('email_click_latency_minutes', minutesToFirstClick); } ``` ## Common Errors | Status | Cause | | ------ | -------------------------------------------------------------------------------------------------- | | `401` | API key missing or malformed. | | `403` | API key lacks the `TRANSACTIONAL` permission. | | `404` | The `id` doesn't exist, or it belongs to a different company than the one this API key authorizes. | ## Polling Guidance * **Open events**: usually surface within seconds for image-proxied clients (Gmail, Apple Mail Privacy Protection). For other clients, wait until the recipient actually opens the message. * **Click events**: stored synchronously the moment the rewritten URL is fetched — they appear in the next stats response. * **Rate**: a steady cadence of one poll every 30–60 seconds for the first \~10 minutes is plenty. Aggressive polling won't surface data faster. ## Related Endpoints * [Send One-Shot HTML Email](/api-reference/messaging-channels/email/send-html) — the producer side of these statistics * [Get Email Template Stats](/api-reference/messaging-channels/email/stats) — aggregated stats for template-backed sends # List Email Templates Source: https://docs.minimo.it/api-reference/messaging-channels/email/list-templates get /public/v1/templates/email Retrieve all email templates available in your Minimo account ## Overview Get a list of all email templates you've created in the Minimo dashboard. Use this endpoint to: * Display available templates in your application * Verify template IDs before sending emails * Build template selection interfaces * Audit your template library ## Response Structure The response includes template metadata: ```json theme={null} { "data": [ { "id": "tmpl_abc123", "name": "Order Confirmation", "subject": "Your order {{orderNumber}} is confirmed", "status": "active", "category": "transactional", "createdAt": "2025-10-01T10:00:00Z", "updatedAt": "2025-11-01T15:30:00Z", "variables": ["orderNumber", "customerName", "total"] }, { "id": "tmpl_xyz789", "name": "Welcome Email", "subject": "Welcome to {{companyName}}!", "status": "active", "category": "marketing", "createdAt": "2025-09-15T09:00:00Z", "updatedAt": "2025-09-15T09:00:00Z", "variables": ["companyName", "firstName"] } ] } ``` ## Template Categories Templates can belong to different categories: | Category | Description | Examples | | --------------- | ------------------------------------ | ---------------------------------- | | `transactional` | Order-related, account notifications | Order confirmation, password reset | | `marketing` | Promotional campaigns | Product launch, newsletter | | `automated` | Triggered by user behavior | Welcome series, abandoned cart | ## Template Status | Status | Description | | ---------- | ---------------------------- | | `active` | Template is ready to use | | `draft` | Template is being edited | | `archived` | Template is no longer in use | Only `active` templates can be used with the Send Email Template endpoint. ## Use Cases ### Template Selector UI Build a dropdown to let users choose templates: ```javascript theme={null} async function loadTemplates() { const response = await fetch('https://api.minimo.it/public/v1/templates/email', { headers: { Authorization: `Bearer ${apiKey}`, }, }); const { data: templates } = await response.json(); // Filter only active templates const activeTemplates = templates.filter((t) => t.status === 'active'); // Populate dropdown const select = document.getElementById('template-select'); activeTemplates.forEach((template) => { const option = document.createElement('option'); option.value = template.id; option.textContent = template.name; select.appendChild(option); }); } ``` ### Validate Template ID Before sending an email, verify the template exists: ```javascript theme={null} async function isValidTemplate(templateId) { const response = await fetch('https://api.minimo.it/public/v1/templates/email', { headers: { Authorization: `Bearer ${apiKey}`, }, }); const { data: templates } = await response.json(); return templates.some((t) => t.id === templateId && t.status === 'active'); } ``` ## Filtering (Coming Soon) Future versions will support filtering: ```bash theme={null} GET /public/v1/templates/email?category=transactional&status=active ``` ## Related Endpoints * [Send Email Template](/api-reference/messaging-channels/email/send-template) - Send emails using templates * [Get Email Template Stats](/api-reference/messaging-channels/email/stats) - View template performance # Send One-Shot HTML Email Source: https://docs.minimo.it/api-reference/messaging-channels/email/send-html post /public/v1/emails Dispatch a single transactional email by sending a pre-rendered HTML payload — no template required ## Overview Use this endpoint when you want to send a transactional email **without creating a template** in the dashboard. You provide the rendered HTML yourself and Minimo handles dispatch, delivery, and tracking. Typical use cases: * **Server-side rendered receipts**: invoices, order confirmations, shipping notifications * **Authentication flows**: magic links, OTP delivery, email verification * **Internal notifications**: alerts produced by your own background jobs * **Anything dynamic enough to not fit a pre-defined template** Tracking (open pixel + click rewriting) is injected **server-side**. Send your raw HTML as-is — don't pre-rewrite links or append a pixel yourself. ## Before You Start 1. Generate an API key with the **`TRANSACTIONAL`** permission from the Minimo dashboard. 2. Make sure your company's email provider is configured (SMTP or SES). 3. Have your HTML body ready — Minimo doesn't render templates for this endpoint. ## What the Server Does to Your HTML When you send `html`, the backend: 1. **Resolves placeholders**: `{{UNSUBSCRIBE_URL}}`, `{{PREFERENCES_URL}}`, and `{{FAKE_IMAGE_URL}}` are replaced with the per-recipient tracked URLs. 2. **Strips any pre-existing tracking pixel**: an `` whose `src` matches the Minimo open-pixel pattern is removed so it can't double-fire. 3. **Rewrites tracked anchors**: every `` pointing to `http(s)` or a relative URL is replaced with a click-tracking URL. `mailto:`, fragment-only (`#…`), and pre-existing `/api/click/*` links are left alone. 4. **Appends the open-tracking pixel** at the end of `` (or at the document root if no `` exists). The recipient receives the final HTML; analytics flow into `GET /public/v1/emails/{id}/stats`. ## Example Request ```bash theme={null} curl --request POST \ --url https://api.minimo.it/public/v1/emails \ --header 'Authorization: Bearer mn-{apiClientId}-{secret}' \ --header 'Content-Type: application/json' \ --data '{ "to": "customer@example.com", "subject": "Your receipt", "html": "

Thanks for your purchase. View receipt

" }' ``` ## Response Shape The endpoint **always returns HTTP 200** when authentication succeeds — inspect `status` to know whether the provider accepted the message. ```json theme={null} { "data": { "id": "DMPNEF8", "status": "sent", "messageId": "0100019c8b3d4a4f-...", "statsUrl": "/public/v1/emails/DMPNEF8/stats" } } ``` When the provider rejects the message you still get back the same `id`, so the delivery row remains queryable: ```json theme={null} { "data": { "id": "DMPNEF8", "status": "failed", "failureReason": "Email address is not verified.", "statsUrl": "/public/v1/emails/DMPNEF8/stats" } } ``` ## Use Cases Render the receipt server-side and post it raw — Minimo rewrites the `View receipt` anchor into a tracked link. ```json theme={null} { "to": "customer@example.com", "subject": "Receipt for ORD-12345", "html": "

Thanks, Jane!

Your order ORD-12345 is on the way.

Track your order

" } ```
Override the sender per request to brand the email for a specific product surface. ```json theme={null} { "to": "newuser@example.com", "subject": "Sign in to Acme", "html": "

Hi! Click here to sign in. Link expires in 15 minutes.

", "fromAddress": "no-reply@acme.com", "fromName": "Acme Sign-In" } ```
Plain HTML with a single CTA is enough — the open and click events surface in the stats endpoint. ```json theme={null} { "to": "ops@example.com", "subject": "[ALERT] Queue depth above threshold", "html": "

Worker queue at 12,400 jobs.

Open dashboard

", "text": "Worker queue at 12,400 jobs. Open dashboard: https://admin.example.com/jobs" } ```
## Best Practices ### Pair every send with a `statsUrl` lookup The response includes `statsUrl`. Persist the `id` (or the full `statsUrl`) alongside whatever business object triggered the send (order id, user id, etc.) so you can correlate opens and clicks later. ### Handle the `failed` branch `status: "failed"` means the provider rejected the message — usually a malformed recipient, an unverified sender, or a hard bounce. Surface `failureReason` to your operators, then either retry with corrected data or log the failure. ### Keep HTML self-contained Inline your CSS. Many email clients drop `