# MailerDash Docs — full text > Complete MailerDash API documentation (https://docs.mailerdash.com). API base URL: https://api.mailerdash.com. Generated for AI agents/LLMs from the docs source. # MailerDash — Documentation URL: https://docs.mailerdash.com/en/ Welcome to the MailerDash documentation. (Pages without an English translation fall back to Spanish.) --- # Campaigns URL: https://docs.mailerdash.com/en/bulk/campaigns/ A campaign sends an email to all active contacts in a list. The process is: create the campaign (in `draft` status) → optional: test it → send or schedule → analytics update in real time as the send progresses. ## Operations ### Management **List campaigns** ``` GET /v1/bulk/campaigns ``` Optional parameters: `status`, `limit` (default `100`), `offset`, `key_id`. **Create campaign** ``` POST /v1/bulk/campaigns ``` Required body: | Field | Type | Description | |---|---|---| | `name` | string | Internal name of the campaign. | | `template_id` | string | ID of the template to send. | | `list_id` | string | ID of the target contact list. | | `from_email` | string | Sender address. Must be a verified domain. | Optional body: `from_name`, `reply_to`. Response: **201** with the campaign object in `status: "draft"`. **Get campaign** ``` GET /v1/bulk/campaigns/{id} ``` **Update campaign** ``` PATCH /v1/bulk/campaigns/{id} ``` Only available when `status` is `draft` or `scheduled`. Updatable fields: `name`, `template_id`, `list_id`, `from_email`, `from_name`, `reply_to`, `scheduled_at`. ### Sending and control **Send or schedule** ``` POST /v1/bulk/campaigns/{id}/send ``` Optional body: `{ "scheduled_at": "2026-07-01T09:00:00Z" }`. Without a body (or without `scheduled_at`) the send is immediate. Response: **200**. **Send test email** ``` POST /v1/bulk/campaigns/{id}/test ``` Required body: `{ "to": "yo@miempresa.com" }`. Optional body: `{ "sample_data": {} }`. Renders the template with sample data and sends it to the specified address. Response: **200**. **Pause** ``` POST /v1/bulk/campaigns/{id}/pause ``` Pauses a send in progress. Response: **200**. Returns **409** if the campaign is not in `sending`. **Resume** ``` POST /v1/bulk/campaigns/{id}/resume ``` Resumes a paused campaign. Response: **200**. Returns **409** if the campaign is not in `paused`. **Cancel** ``` POST /v1/bulk/campaigns/{id}/cancel ``` Cancels the campaign. Available from `draft`, `scheduled`, `sending`, or `paused`. Returns **409** if already in `sent` or `cancelled`. **Duplicate** ``` POST /v1/bulk/campaigns/{id}/duplicate ``` Creates a new `draft` with the same parameters. Response: **201** with the new campaign object. ### Analytics **Detailed analytics** ``` GET /v1/bulk/campaigns/{id}/analytics ``` Returns engagement metrics: opens (unique, total, rate), clicks (unique, total, rate, ctor), bounces, complaints, unsubs, rates (open/ctr/ctor/bounce/complaint/unsub), `top_links[]` and `timeline[]`. Optional parameter: `format=csv` to export. **Delivery report** ``` GET /v1/bulk/campaigns/{id}/report ``` Delivery statistics for the campaign. **Deliverability** ``` GET /v1/bulk/campaigns/{id}/deliverability ``` Crosses sends + bounces + complaints. Includes `delivery_rate`, `bounce_rate`, `complaint_rate` and a breakdown by destination domain. **Cross-campaign performance** ``` GET /v1/bulk/campaigns/performance ``` Optional parameter: `key_id`. Returns an array of objects with `id`, `name`, `status`, `started_at`, `delivered`, `opens_unique`, `clicks_unique`, `bounced`, `open_rate`, `click_rate`, and `bounce_rate`. **Aggregated rates** ``` GET /v1/bulk/analytics/rates ``` Optional parameters: `period` (`24h` | `7d` | `30d`, default `7d`), `key_id`, `format=csv`. Returns `period`, `granularity`, `window_start`, `totals`, `rates`, and `series[]`. ## Lifecycle A campaign's status follows this flow: ``` draft │ ├─ /test (test send, does not change status) │ └─ /send ──► sending ──► sent │ ├─ /pause ──► paused │ │ └───────────────┘ /resume │ └─ /cancel ──► cancelled /cancel also available from: draft, scheduled, paused ``` The possible states are: `draft`, `scheduled`, `sending`, `sent`, `paused`, `cancelled`. A campaign in `sent` is immutable — it cannot be resent. ## Full example ```bash # 1. Create the campaign curl -X POST https://api.mailerdash.com/v1/bulk/campaigns \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Newsletter Junio 2026", "template_id": "bienvenida-fc9674", "list_id": "newsletter-q3-2026-abc123", "from_email": "noticias@miempresa.com", "from_name": "Mi Empresa", "reply_to": "contacto@miempresa.com" }' # 2. Send a test email to your inbox curl -X POST https://api.mailerdash.com/v1/bulk/campaigns/cmp-d894cd3d626d9d11/test \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to": "yo@miempresa.com"}' # 3. Launch the bulk send curl -X POST https://api.mailerdash.com/v1/bulk/campaigns/cmp-d894cd3d626d9d11/send \ -H "Authorization: Bearer $MAILERDASH_API_KEY" ``` ## Reference For the full request/response schema and error codes, see the [bulk API reference](/reference/bulk/). --- # Contacts URL: https://docs.mailerdash.com/en/bulk/contacts/ Contacts are the foundation of the entire bulk system. Each contact has an `email` as its unique identifier within your API key, plus optional fields: `name` and `metadata` (a free-form JSON object for any segmentation data you need). A contact can belong to multiple lists and can have one of four statuses: `active`, `unsubscribed`, `bounced`, or `complained`. ## Schema | Field | Type | Description | |---|---|---| | `id` | integer | Internal identifier. | | `email` | string | Contact's email. Unique per key. | | `name` | string | Optional name. | | `metadata` | object | Free-form JSON object for custom attributes. | | `status` | string | `active` \| `unsubscribed` \| `bounced` \| `complained` | | `key_id` | string | API key the contact belongs to. | | `created_at` | string | Creation date (ISO 8601). | | `updated_at` | string | Last updated date (ISO 8601). | ## Operations ### List contacts ``` GET /v1/bulk/contacts ``` Query parameters: | Parameter | Description | |---|---| | `limit` | Maximum number of results (default: `100`). | | `offset` | Offset for pagination. | | `status` | Filter by status: `active`, `unsubscribed`, `bounced`, `complained`. | | `q` / `search` | Search by email or name. | | `key_id` | Filter by API key (admin only). | | `format` | `csv` to export in CSV format. | --- ### Create or update a contact ``` POST /v1/bulk/contacts ``` Body: ```json { "email": "ana@empresa.com", "name": "Ana García", "metadata": { "plan": "pro" } } ``` If a contact with that `email` already exists, their data is updated. Returns **201** with the full contact. --- ### Get a contact ``` GET /v1/bulk/contacts/{email} ``` Returns the contact with that email, including all their fields. --- ### Update a contact ``` PATCH /v1/bulk/contacts/{email} ``` Body (all fields are optional): ```json { "name": "Ana García López", "status": "unsubscribed", "metadata": { "plan": "enterprise" } } ``` --- ### Delete a contact ``` DELETE /v1/bulk/contacts/{email} ``` Permanently deletes the contact. Returns **204 No Content**. --- ### Engagement history ``` GET /v1/bulk/contacts/{email}/engagement ``` Returns the history of campaigns received by that contact and their engagement score. Response: ```json { "email": "ana@empresa.com", "sends": [ { "campaign_id": "camp_abc123", "campaign_name": "Newsletter Q2", "sent_at": "2026-04-15T10:00:00Z", "opened": true, "clicked": false } ], "totals": { "sends": 5, "opens": 3, "clicks": 1 }, "score": "active", "last_engagement_at": "2026-04-15T14:22:00Z" } ``` The `score` field can be `active`, `passive`, or `dormant` based on the contact's recent activity. --- ### Bulk import contacts ``` POST /v1/bulk/contacts/import ``` Body: array of objects with `email` (required), `name` and `metadata` optional. ```json [ { "email": "ana@empresa.com", "name": "Ana García", "metadata": { "plan": "pro" } }, { "email": "pedro@cliente.io", "name": "Pedro Sánchez" }, { "email": "maria@otro.com" } ] ``` Returns **207 Multi-Status** with the result for each entry: ```json { "created": 2, "updated": 1, "failed": 0, "errors": [] } ``` --- ### Import preview ``` POST /v1/bulk/contacts/import-preview ``` Checks how many emails already exist in your account before importing. Does not modify any data. Body: ```json { "emails": ["ana@empresa.com", "nuevo@dominio.com"] } ``` Response: ```json { "existing": ["ana@empresa.com"], "total_unique": 2, "total_received": 2 } ``` --- ### Bulk delete contacts ``` POST /v1/bulk/contacts/delete ``` Atomic deletion of multiple contacts. Body: ```json { "emails": ["ana@empresa.com", "pedro@cliente.io"] } ``` Response: ```json { "requested": 2, "deleted": 2, "not_found": [], "forbidden": [] } ``` ## Examples ### Create a contact with metadata ```bash curl -X POST https://api.mailerdash.com/v1/bulk/contacts \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "ana@empresa.com", "name": "Ana García", "metadata": {"plan": "pro", "signup_source": "landing"}}' ``` ### Bulk import contacts ```bash curl -X POST https://api.mailerdash.com/v1/bulk/contacts/import \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '[ {"email": "ana@empresa.com", "name": "Ana García", "metadata": {"plan": "pro"}}, {"email": "pedro@cliente.io", "name": "Pedro Sánchez", "metadata": {"region": "CDMX"}}, {"email": "maria@otro.com"} ]' ``` ## Reference For the full request/response schema and error codes, see the [bulk API reference](/reference/bulk/). --- # Lists URL: https://docs.mailerdash.com/en/bulk/lists/ A list is a group of contacts you can target with a campaign. Each campaign points to exactly one list, and a list can contain any number of active contacts. Lists are independent of each other: a contact can belong to multiple lists at the same time, and removing them from one list does not affect them in others or delete them from the system. ## Schema | Field | Type | Description | |---|---|---| | `id` | string | Slug identifier for the list. | | `name` | string | Descriptive name. | | `key_id` | string | API key the list belongs to. | | `contact_count` | integer | Number of contacts in the list. | | `created_at` | string | Creation date (ISO 8601). | ## Operations ### List all lists ``` GET /v1/bulk/lists ``` Returns an array with all lists in your account, including the `contact_count` for each one. --- ### Create a list ``` POST /v1/bulk/lists ``` Body: ```json { "name": "Newsletter Q3 2026" } ``` Returns **201** with the created list, including its automatically generated `id`. --- ### Get a list with its contacts ``` GET /v1/bulk/lists/{id} ``` Returns the list details along with its paginated contacts. Query parameters: | Parameter | Description | |---|---| | `limit` | Maximum number of contacts to return. | | `offset` | Offset for pagination. | | `status` | Filter contacts by status. | --- ### Rename a list ``` PATCH /v1/bulk/lists/{id} ``` Body: ```json { "name": "Newsletter Q4 2026" } ``` --- ### Delete a list ``` DELETE /v1/bulk/lists/{id} ``` Deletes the list. Returns **204 No Content**. If the list is referenced by an active campaign, returns **409 Conflict**. --- ### Add contacts to a list ``` POST /v1/bulk/lists/{id}/contacts ``` Body: ```json { "emails": ["ana@empresa.com", "pedro@cliente.io"] } ``` The emails must correspond to contacts that already exist in your account. Returns **200** with the number of contacts added. --- ### Remove a contact from a list ``` DELETE /v1/bulk/lists/{id}/contacts/{email} ``` Removes the contact from this list. Returns **204 No Content**. The contact continues to exist in the system and in any other list it belongs to. --- ### Clone a list ``` POST /v1/bulk/lists/{id}/duplicate ``` Creates a copy of the list with all its contacts. Useful for creating derived segments without starting from scratch. Returns **201** with the new list. --- ### Bulk delete lists ``` POST /v1/bulk/lists/delete ``` Atomic deletion of multiple lists. Body: ```json { "ids": ["newsletter-q3-abc123", "promo-verano-xyz"] } ``` Response: ```json { "requested": 2, "deleted": 1, "not_found": [], "forbidden": [], "in_use": ["promo-verano-xyz"] } ``` Lists referenced by a campaign are reported in `in_use` and are not deleted. The remaining ones are deleted, even within the same request. ## Examples ### Create a list and add contacts ```bash # 1. Create the list curl -X POST https://api.mailerdash.com/v1/bulk/lists \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Newsletter Q3 2026"}' # 2. Add contacts (use emails already registered as contacts) curl -X POST https://api.mailerdash.com/v1/bulk/lists/newsletter-q3-2026-abc123/contacts \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"emails": ["ana@empresa.com", "pedro@cliente.io"]}' ``` ## Reference For the full request/response schema and error codes, see the [bulk API reference](/reference/bulk/). --- # Overview (Bulk / Marketing) URL: https://docs.mailerdash.com/en/bulk/overview/ The bulk (marketing) module is independent from the transactional module. It uses the same authentication — `Authorization: Bearer $MAILERDASH_API_KEY` — but operates on a different channel (`X-Md-Channel: bulk`). You do not need to add that header to your REST calls: the bulk-worker adds it automatically when dispatching campaigns. Your interaction with the API is always to manage data (contacts, lists, templates, campaigns, sequences), not to trigger sends directly. ## Data model The general flow is: **Contacts** → added to **Lists** → **Campaigns** (or **Sequences**) point to a List + a Template → the bulk-worker dispatches the sends. ## Resources ### Contacts Individual records with `email`, `name`, and arbitrary metadata. Each contact has a `status` that reflects their deliverability state: - `active` — can receive emails. - `unsubscribed` — unsubscribed; will not receive further messages. - `bounced` — a permanent bounce (5.x.x) occurred; excluded from future sends. - `complained` — marked an email as spam via FBL; excluded from future sends. Contacts are the source of truth for your subscribers. ### Lists Groups of contacts. A campaign points to exactly one list. You can have separate lists per product, segment, or channel (newsletter, onboarding, reactivation). Contacts can belong to multiple lists. ### Templates Reusable HTML and/or plain-text content, with its own `subject`. Campaigns and sequences reference them by ID, letting you update the content without modifying already-configured campaigns. ### Campaigns One-shot bulk send to a list. Lifecycle: - `draft` — draft state, editable. - `send` — queued for immediate dispatch. - `sent` — the bulk-worker finished processing all contacts. Once sent, a campaign is not modifiable. ### Sequences Drip automations. A sequence defines steps (`steps`) with a delay between each one (hours or days), and has `subscribers` — contacts that advance through the steps at the configured intervals. Useful for automated onboarding, nurturing, and follow-ups. ## Suppressions All bulk routes respect the account's global suppression list. Contacts with status `bounced`, `complained`, or `unsubscribed` are automatically skipped at send time — you don't need to filter them manually. See the [suppressions](/en/suppressions/) guide to learn how to manage the list and what happens when a contact is suppressed from different sources. ## Guides by resource | Resource | Guide | |---|---| | Contacts | [Create, import, and manage contacts](/en/bulk/contacts/) | | Lists | [Organize contacts into lists](/en/bulk/lists/) | | Templates | [Create reusable templates](/en/bulk/templates/) | | Campaigns | [Send bulk campaigns](/en/bulk/campaigns/) | | Sequences | [Automate with sequences (drip)](/en/bulk/sequences/) | --- # Sequences (drip) URL: https://docs.mailerdash.com/en/bulk/sequences/ A sequence is a drip automation: you define a series of emails (steps) with delays between them, then subscribe contacts. The system sends each step at the right time without manual intervention. They are ideal for onboarding, nurturing, and post-conversion follow-ups. ## Key concepts - **Sequence** — the container. Has a name and `status` (`draft` / `active` / `archived`). Must be in `active` for the worker to process sends. - **Step** — an email within the sequence. Has `position` (execution order), `offset_seconds` (delay from subscription or from the previous step), and the email content: `subject` + `html` / `body_text`, or a `template_id`. At least one of the two content schemes must be present. - **Subscriber** — a contact subscribed to the sequence. Advances through the steps over the configured time, and can be in active, paused, completed, or cancelled state. ## Operations ### Sequences **List** ``` GET /v1/bulk/sequences ``` **Create** ``` POST /v1/bulk/sequences ``` Required body: `{ "name": "Sequence name" }`. Response: **201** with `status: "draft"`. **Get with its steps** ``` GET /v1/bulk/sequences/{id} ``` **Update** ``` PATCH /v1/bulk/sequences/{id} ``` Body: `{ "name"?: string, "status"?: "draft" | "active" | "archived" }`. **Delete** ``` DELETE /v1/bulk/sequences/{id} ``` Returns **409** if the sequence has active or paused subscribers. ### Steps **Add step** ``` POST /v1/bulk/sequences/{id}/steps ``` Required body: | Field | Type | Description | |---|---|---| | `position` | integer | Order of the step within the sequence (1, 2, 3…). | | `offset_seconds` | integer | Seconds to wait before sending this step. | Optional body: `subject`, `html`, `body_text`, `from_email`, `from_name`, `reply_to`, `template_id`. Response: **201** with the step object. Returns **403** if `from_email` uses a domain not authorized on the account. **Update step** ``` PATCH /v1/bulk/sequences/{id}/steps/{step_id} ``` Accepts the same optional fields as the POST. Returns **403** if `from_email` uses an unauthorized domain. **Delete step** ``` DELETE /v1/bulk/sequences/{id}/steps/{step_id} ``` Response: **204**. ### Subscribers **List subscribers** ``` GET /v1/bulk/sequences/{id}/subscribers ``` **Subscribe a contact** ``` POST /v1/bulk/sequences/{id}/subscribers ``` The operation is idempotent: if the contact already has an active or paused subscription, it returns **200** without creating a duplicate. Required body: `{ "email": "contacto@example.com" }`. Optional body: `{ "started_at"?: string (ISO 8601), "variables"?: object }`. Response: **201** (new subscription) or **200** (already existed as active/paused). Returns **409** if the subscription exists in a terminal state (`completed` or `cancelled`). **Get subscriber status** ``` GET /v1/bulk/sequences/{id}/subscribers/{email} ``` **Cancel subscription** ``` DELETE /v1/bulk/sequences/{id}/subscribers/{email} ``` Response: **200**. **Pause** ``` POST /v1/bulk/sequences/{id}/subscribers/{email}/pause ``` Response: **200**. Pending steps are held until resumed. **Resume** ``` POST /v1/bulk/sequences/{id}/subscribers/{email}/resume ``` Response: **200**. ## Example: create a welcome sequence ```bash # 1. Create the sequence curl -X POST https://api.mailerdash.com/v1/bulk/sequences \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Onboarding - Bienvenida"}' # Response: { "id": "onboarding-bienvenida-a1b2c3", "status": "draft", ... } # 2. Add the first step (immediate, offset_seconds=0) curl -X POST https://api.mailerdash.com/v1/bulk/sequences/onboarding-bienvenida-a1b2c3/steps \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "position": 1, "offset_seconds": 0, "subject": "Bienvenido a {{name}}, aquí empieza todo", "from_email": "hola@miempresa.com", "template_id": "bienvenida-fc9674" }' # 3. Add the second step (3 days later = 259200 seconds) curl -X POST https://api.mailerdash.com/v1/bulk/sequences/onboarding-bienvenida-a1b2c3/steps \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "position": 2, "offset_seconds": 259200, "subject": "¿Cómo va todo?", "from_email": "hola@miempresa.com", "template_id": "followup-fc9675" }' # 4. Activate the sequence curl -X PATCH https://api.mailerdash.com/v1/bulk/sequences/onboarding-bienvenida-a1b2c3 \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "active"}' # 5. Subscribe a contact curl -X POST https://api.mailerdash.com/v1/bulk/sequences/onboarding-bienvenida-a1b2c3/subscribers \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "ana@empresa.com"}' ``` ## Sequence webhooks If you have webhooks configured on your API key, you will receive events for each moment in a subscription's lifecycle: | Event | When it fires | |---|---| | `sequence.subscribed` | A new contact subscribes. | | `sequence.step.sent` | A step is sent successfully. | | `sequence.step.failed` | A step fails to send. | | `sequence.completed` | The contact completed all steps. | | `sequence.cancelled` | The subscription is cancelled. Includes a `reason` field: `unsubscribed`, `manual`, `bounced`, `complained`, or `suppressed`. | ## Reference For the full request/response schema and error codes, see the [bulk API reference](/reference/bulk/). --- # Templates URL: https://docs.mailerdash.com/en/bulk/templates/ A template defines the subject and body (HTML and/or plain text) of emails. Campaigns and sequences reference it by `id` — you can update the template without touching the campaign, and change the content before sending. This lets you iterate on copy or design without having to edit each campaign separately. ## Schema | Field | Type | Description | |---|---|---| | `id` | string | Slug identifier for the template. | | `name` | string | Descriptive name to identify it in the dashboard. | | `subject` | string | Email subject. Supports merge tags. | | `html` | string | HTML body. Supports merge tags. | | `body_text` | string | Plain-text body. Supports merge tags. | | `key_id` | string | API key this template belongs to. | | `created_at` | string | Creation date (ISO 8601). | | `updated_at` | string | Last updated date (ISO 8601). | ## Merge tags You can personalize the subject and body using `{{name}}` to insert the contact's name. MailerDash replaces the tag at send time with the value of the `name` field on the recipient contact. Example: ``` subject: "Hi {{name}}, your report is ready" html: "
Your monthly report is available.
" ``` If the contact has no `name`, the tag is replaced with an empty string. ## Operations ### List templates ``` GET /v1/bulk/templates ``` Returns an array with all templates in your account. --- ### Create a template ``` POST /v1/bulk/templates ``` Body: ```json { "name": "Bienvenida onboarding", "subject": "Bienvenido a bordo, {{name}}", "html": "Tu cuenta está lista.
", "body_text": "Hola {{name}},\n\nTu cuenta está lista." } ``` `name` and `subject` are required. Returns **201** with the created template. --- ### Get a template ``` GET /v1/bulk/templates/{id} ``` Returns all fields of the template, including `html` and `body_text`. --- ### Update a template ``` PATCH /v1/bulk/templates/{id} ``` Body (all fields are optional): ```json { "subject": "¡Bienvenido, {{name}}!", "html": "Tu cuenta está lista. Empieza ahora.
" } ``` Returns **200** with the updated template. Fields you don't send are not modified. --- ### Delete a template ``` DELETE /v1/bulk/templates/{id} ``` Deletes the template. Returns **204 No Content**. If it is referenced by an active campaign, returns **409 Conflict**. --- ### Bulk delete templates ``` POST /v1/bulk/templates/delete ``` Atomic deletion of multiple templates. Body: ```json { "ids": ["bienvenida-onboarding-abc", "promo-verano-xyz"] } ``` Response: ```json { "requested": 2, "deleted": 1, "not_found": [], "forbidden": [], "in_use": ["promo-verano-xyz"] } ``` Templates in use by active campaigns are reported in `in_use` and are not deleted. The rest are deleted in the same request. ## Example ### Create a template with HTML and plain text ```bash curl -X POST https://api.mailerdash.com/v1/bulk/templates \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Bienvenida onboarding", "subject": "Bienvenido a bordo, {{name}}", "html": "Tu cuenta está lista.
", "body_text": "Hola {{name}},\n\nTu cuenta está lista." }' ``` ## Reference For the full request/response schema and error codes, see the [bulk API reference](/reference/bulk/). --- # SPF, DKIM, and DMARC URL: https://docs.mailerdash.com/en/domains/spf-dkim-dmarc/ The three email authentication protocols (SPF, DKIM, and DMARC) tell receiving servers that your emails are legitimate. Without them, Gmail, Outlook, and Yahoo may send them to spam or reject them outright. --- ## SPF — who can send on your behalf SPF (Sender Policy Framework) lists the servers authorized to send email from your domain. Receiving servers verify that the sender's IP is on that list. ### Record to publish Add (or modify) the TXT record at the root of your domain: ``` _Name_: tuempresa.com (or @) _Type_: TXT _Value_: v=spf1 include:mailerdash.com ~all ``` If you already have an existing SPF record (for example from Google Workspace or another provider), **do not create a second TXT** — add the `include` to your existing record: ``` v=spf1 include:_spf.google.com include:mailerdash.com ~all ``` --- ## DKIM — cryptographic signature of content DKIM (DomainKeys Identified Mail) signs each email with a private key. The receiving server verifies the signature using the public key published in your DNS. ### CNAME delegation (one-time setup, forever) MailerDash uses **CNAME delegation**: you publish a CNAME once and the platform rotates the internal key without you ever having to touch your DNS again. ``` _Name_:Hola, gracias por registrarte.
" }' ``` ### Node.js ```js const response = await fetch('https://api.mailerdash.com/v1/mail/send', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MAILERDASH_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from: { email: 'noreply@tu-dominio.com', name: 'Tu Empresa' }, to: [{ email: 'cliente@ejemplo.com' }], subject: 'Bienvenido', text: 'Hola, gracias por registrarte.', html: 'Hola, gracias por registrarte.
', }), }); if (!response.ok) { const { error } = await response.json(); console.error(error.type, error.code, error.message); } else { const data = await response.json(); console.log('Enviado, message_id:', data.message_id); } ``` ### Python ```python response = httpx.post( "https://api.mailerdash.com/v1/mail/send", headers={"Authorization": f"Bearer {os.environ['MAILERDASH_API_KEY']}"}, json={ "from": {"email": "noreply@tu-dominio.com", "name": "Tu Empresa"}, "to": [{"email": "cliente@ejemplo.com"}], "subject": "Bienvenido", "text": "Hola, gracias por registrarte.", "html": "Hola, gracias por registrarte.
", }, ) if response.is_error: error = response.json()["error"] print(f"Error {error['type']}: {error['code']} — {error['message']}") else: print("Enviado:", response.json().get("message_id")) ``` ### PHP With [Guzzle](https://docs.guzzlephp.org/) (`composer require guzzlehttp/guzzle`): ```php post('https://api.mailerdash.com/v1/mail/send', [ 'headers' => ['Authorization' => 'Bearer ' . getenv('MAILERDASH_API_KEY')], 'json' => [ 'from' => ['email' => 'noreply@tu-dominio.com', 'name' => 'Tu Empresa'], 'to' => [['email' => 'cliente@ejemplo.com']], 'subject' => 'Bienvenido', 'text' => 'Hola, gracias por registrarte.', 'html' => 'Hola, gracias por registrarte.
', ], ]); $data = json_decode((string) $response->getBody(), true); echo 'Enviado, message_id: ' . $data['message_id'] . PHP_EOL; } catch (RequestException $e) { $error = json_decode((string) $e->getResponse()->getBody(), true)['error']; fprintf(STDERR, "Error %s: %s — %s\n", $error['type'], $error['code'], $error['message']); } ``` ### Laravel Using Laravel's HTTP client (`Illuminate\Support\Facades\Http`): ```php use Illuminate\Support\Facades\Http; $response = Http::withToken(env('MAILERDASH_API_KEY')) ->post('https://api.mailerdash.com/v1/mail/send', [ 'from' => ['email' => 'noreply@tu-dominio.com', 'name' => 'Tu Empresa'], 'to' => [['email' => 'cliente@ejemplo.com']], 'subject' => 'Bienvenido', 'text' => 'Hola, gracias por registrarte.', 'html' => 'Hola, gracias por registrarte.
', ]); if ($response->failed()) { $error = $response->json('error'); logger()->error("MailerDash {$error['type']}: {$error['code']} — {$error['message']}"); } else { logger()->info('Enviado, message_id: ' . $response->json('message_id')); } ``` ### WordPress With `wp_remote_post`. Define your API key as a constant in `wp-config.php` (`define('MAILERDASH_API_KEY', 'tu-api-key');`): ```php $response = wp_remote_post('https://api.mailerdash.com/v1/mail/send', array( 'headers' => array( 'Authorization' => 'Bearer ' . MAILERDASH_API_KEY, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode(array( 'from' => array('email' => 'noreply@tu-dominio.com', 'name' => 'Tu Empresa'), 'to' => array(array('email' => 'cliente@ejemplo.com')), 'subject' => 'Bienvenido', 'text' => 'Hola, gracias por registrarte.', 'html' => 'Hola, gracias por registrarte.
', )), 'timeout' => 15, )); if (is_wp_error($response)) { error_log('MailerDash: ' . $response->get_error_message()); } elseif (wp_remote_retrieve_response_code($response) >= 400) { $error = json_decode(wp_remote_retrieve_body($response), true)['error']; error_log('MailerDash error: ' . $error['type'] . ' — ' . $error['message']); } else { $data = json_decode(wp_remote_retrieve_body($response), true); error_log('MailerDash enviado, message_id: ' . $data['message_id']); } ``` --- ## API reference The interactive reference (Swagger UI) is available directly in the dashboard: | Section | URL | |---|---| | Transactional (individual sends) | [/reference/transactional/](/reference/transactional/) | | Bulk (campaigns, contacts, lists, sequences) | [/reference/bulk/](/reference/bulk/) | | Platform (keys, domains, webhooks, usage) | [/reference/platform/](/reference/platform/) | You can also explore the API interactively (Swagger UI) or download the public OpenAPI spec (client endpoints only) as JSON: ```bash # Public OpenAPI spec (JSON) — useful for generating SDKs or importing to Postman curl https://api.mailerdash.com/docs.json -o mailerdash-openapi.json ``` - **Interactive Swagger UI:** [https://api.mailerdash.com/docs](https://api.mailerdash.com/docs) - **JSON spec:** `https://api.mailerdash.com/docs.json` --- ## For agents and LLMs If you build with an AI agent or an LLM, these docs are *agent-friendly*: | Resource | URL | |---|---| | LLM index | [`/llms.txt`](https://docs.mailerdash.com/llms.txt) | | Full docs in plain text | [`/llms-full.txt`](https://docs.mailerdash.com/llms-full.txt) | | Raw OpenAPI — Transactional | [`/openapi/openapi.transactional.json`](https://docs.mailerdash.com/openapi/openapi.transactional.json) | | Raw OpenAPI — Bulk | [`/openapi/openapi.bulk.json`](https://docs.mailerdash.com/openapi/openapi.bulk.json) | | Raw OpenAPI — Platform | [`/openapi/openapi.platform.json`](https://docs.mailerdash.com/openapi/openapi.platform.json) | `llms-full.txt` bundles the entire documentation into a single file, ready to paste into a model's context. --- ## Generate your SDK We have not published an official library (`npm`/`pip`) yet, but the public OpenAPI spec (`docs.json`, above) lets you generate a **typed** client in your language with [openapi-generator](https://openapi-generator.tech/) — without waiting for an official package. ### TypeScript ```bash npx @openapitools/openapi-generator-cli generate \ -i https://api.mailerdash.com/docs.json \ -g typescript-fetch \ -o ./mailerdash-sdk ``` ### Python ```bash npx @openapitools/openapi-generator-cli generate \ -i https://api.mailerdash.com/docs.json \ -g python \ -o ./mailerdash-sdk-python ``` The CLI runs on the JVM, so you need **Java 11+** installed. There are generators for more than 50 languages (PHP, Ruby, Go, C#, etc.); see the full list with `npx @openapitools/openapi-generator-cli list`. Since the spec is kept in sync with the production API, regenerating your SDK after a version change is just a matter of running the command again. --- ## Support If you have questions, find a bug, or need help with your integration, open a ticket from the dashboard or write to us directly. The ticket system lets you track each case and attach logs or payload examples for faster diagnosis. - **From the dashboard**: sidebar menu → Support → New ticket - **Via API**: `POST /v1/tickets` with the `subject` and `body` fields We will respond within the business hours published in the dashboard. For critical production incidents, indicate it in the ticket subject. --- # Suppressions URL: https://docs.mailerdash.com/en/suppressions/ The suppression list is the mechanism that protects your sending reputation. Any address that generates a permanent bounce, a spam complaint, or unsubscribes is added to the list automatically, and **the platform skips that email on all future sends** — both transactional and bulk — without you having to do anything. --- ## How entries are added Suppressions are created in three ways: | Source | `reason` | Description | |--------|---------|-------------| | Permanent bounce (5xx) | `bounce` | The receiving server permanently rejected the address | | FBL / spam complaint | `complaint` | The recipient marked the email as spam | | Unsubscribe | `unsubscribed` | The contact clicked the unsubscribe link or called the unsubscribe endpoint | | Manual | `manual` | An admin added the address manually via API or dashboard | --- ## Operations - **`GET /v1/suppressions`** — list all suppressed addresses, paginated. Query params: `limit` (default 100, max 1000), `offset`, `reason` (`bounce`|`complaint`|`manual`), `email` (partial search). - **`POST /v1/suppressions`** — manually add an address (requires admin key). Body: `{ email: string, reason?: "manual"|"complaint" }`. Useful for importing your own opt-out lists. - **`DELETE /v1/suppressions/{email}`** — "remove" a suppression so the address can receive emails again (requires admin key). Returns 204. --- ## Example: list recent suppressions ```bash # List the last 20 bounce suppressions curl "https://api.mailerdash.com/v1/suppressions?reason=bounce&limit=20" \ -H "Authorization: Bearer $MAILERDASH_API_KEY" ``` Response: ```json { "total": 47, "limit": 20, "offset": 0, "items": [ { "email": "usuario@dominio.com", "reason": "bounce", "code": "5.1.1", "added_at": "2026-06-15T10:30:00.000Z" } ] } ``` ## Example: add a manual suppression ```bash curl -X POST https://api.mailerdash.com/v1/suppressions \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "optout@cliente.com", "reason": "manual"}' ``` ## Example: remove a suppression ```bash curl -X DELETE "https://api.mailerdash.com/v1/suppressions/usuario%40dominio.com" \ -H "Authorization: Bearer $MAILERDASH_API_KEY" ``` --- ## GDPR / privacy considerations If a user exercises their right to data erasure (right to be forgotten), the correct process in MailerDash is: 1. Delete the contact via `DELETE /v1/bulk/contacts/{email}` — this removes them from your lists. 2. **Do not** remove the suppression — keeping it protects the user from receiving accidental future emails. The user's account on the platform (if one exists) is anonymized (email/name replaced with placeholders) when deleted, but suppressions remain as protection, not as personally identifiable data. --- **API reference**: [Platform — Suppressions](/reference/platform/) --- # Statistics, bounces, and audit URL: https://docs.mailerdash.com/en/transactional/events-and-stats/ These three endpoints give you complete visibility into what happened with your emails: how many were sent, which ones bounced, and a detailed record of each event. Use them to monitor the health of your deliverability, debug delivery issues, and generate activity reports. ## Available operations ### GET /v1/mail/stats Returns aggregated server metrics for the specified period. ``` GET https://api.mailerdash.com/v1/mail/stats ``` **Query params:** | Parameter | Type | Default | Description | |---|---|---|---| | `hours` | integer | 24 | Lookback time window. Maximum 720 (30 days). | | `key_id` | string | — | Admin only. Filters metrics by a specific API key. | **Example:** ```bash curl https://api.mailerdash.com/v1/mail/stats?hours=48 \ -H "Authorization: Bearer $MAILERDASH_API_KEY" ``` --- ### GET /v1/mail/bounces Lists bounces processed by the system, from most recent to oldest. ``` GET https://api.mailerdash.com/v1/mail/bounces ``` **Query params:** | Parameter | Type | Default | Description | |---|---|---|---| | `limit` | integer | 100 | Number of results. Maximum 1000. | | `offset` | integer | 0 | Pagination cursor. | | `recipient` | string | — | Filters by recipient email address. | | `status` | string | — | Filters by DSN code (e.g. `5.1.1` for unknown user). | | `key_id` | string | — | Admin only. Filters by API key. | **Response:** ```json { "total": 42, "limit": 100, "offset": 0, "items": [ { "id": 17, "recipient": "inexistente@example.com", "status": "5.1.1", "raw": "User unknown", "created_at": "2026-06-21T14:32:00Z" } ] } ``` --- ### GET /v1/mail/audit Audit log of sends and account actions, with support for advanced filters and CSV export. ``` GET https://api.mailerdash.com/v1/mail/audit ``` **Query params:** | Parameter | Type | Default | Description | |---|---|---|---| | `limit` | integer | 100 | Number of results. Maximum 1000. | | `offset` | integer | 0 | Pagination cursor. | | `app` | string | — | Filters by key ID (`app` in the audit). | | `event` | string | — | Filters by event type: `sent`, `failed`, `queued`. Accepts CSV for multiple: `sent,failed`. | | `from` | string | — | ISO 8601 start timestamp for the range (e.g. `2026-06-01T00:00:00Z`). | | `to` | string | — | ISO 8601 end timestamp for the range. | | `search` | string | — | Substring search against the recipient or subject. | | `sort_by` | string | `ts` | Sort field: `ts`, `event`, `subject`. | | `sort_dir` | string | `desc` | Direction: `asc` or `desc`. | | `format` | string | `json` | `json` (paginated) or `csv` (full download, ignores `limit`/`offset`). | **JSON response:** ```json { "total": 1240, "limit": 100, "offset": 0, "items": [ { "id": 998, "ts": "2026-06-22T09:15:00Z", "event": "sent", "app": "Haz clic aquí para confirmar.
" }, { "type": "text/plain", "value": "Visita https://example.com/verify para confirmar tu cuenta." } ] } ``` ### Fields | Field | Type | Required | Description | |---|---|---|---| | `personalizations` | array | yes | List of recipient groups. At least one element. | | `personalizations[].to` | array | yes | Recipients in the group. Each item: `{ email, name? }`. | | `from` | object | yes | Sender. `{ email, name? }`. Must be a verified domain on your account. | | `subject` | string | yes | Email subject line. | | `content` | array | yes | Message bodies. At least one. Each item: `{ type, value }`. | | `content[].type` | string | yes | `"text/plain"` or `"text/html"`. Including both is recommended. | | `content[].value` | string | yes | Body content in the specified type. | ## Multiple recipients To send the same message to several recipients in a single request, add more objects to the `to` array inside `personalizations[0]`: ```json { "personalizations": [ { "to": [ { "email": "ana@example.com", "name": "Ana" }, { "email": "beto@example.com", "name": "Beto" }, { "email": "carlos@example.com" } ] } ], "from": { "email": "no-reply@tu-dominio.com" }, "subject": "Actualización de tu cuenta", "content": [{ "type": "text/plain", "value": "Tu cuenta ha sido actualizada." }] } ``` Each address in `to` receives the message independently. ## Transactional channel Include the `X-Md-Channel: trans` header in all your requests. Although the transactional channel is the default behavior, declaring it explicitly is good practice: it makes the intent clear and makes debugging easier if the MTA routing changes in the future. ```bash -H "X-Md-Channel: trans" ``` ## Recipient validation Before queuing any message, MailerDash validates each recipient address in three layers: 1. **Syntax** — the address must have a valid RFC format. 2. **Disposable domains** — a list of more than 5,000 temporary email domains (mailinator, guerrillamail, etc.) is rejected. 3. **MX lookup** — it verifies that the domain has active MX records (result cached for 24 hours). If any address fails any of the three layers, the entire request is rejected with `400 Bad Request`. Validate your lists before sending to large volumes. ## Idempotency If you include the `Idempotency-Key` header with a unique UUID per attempt, the server automatically deduplicates retries: ```bash -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" ``` - Within the **24-hour** window, a second request with the same key returns exactly the same response without re-queuing the message. - If you reuse the same `Idempotency-Key` with a **different payload**, you will receive `422 Unprocessable Entity`. Use idempotency in any retry logic to avoid duplicates. ## Full example ```bash curl -X POST https://api.mailerdash.com/v1/mail/send \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Md-Channel: trans" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "personalizations": [ { "to": [ { "email": "cliente@example.com", "name": "Cliente Ejemplo" } ] } ], "from": { "email": "no-reply@tu-dominio.com", "name": "Tu Servicio" }, "subject": "Confirma tu cuenta en Tu Servicio", "content": [ { "type": "text/html", "value": "Hola Cliente Ejemplo,
Haz clic aquí para confirmar tu cuenta.
El enlace expira en 24 horas.
" }, { "type": "text/plain", "value": "Hola Cliente Ejemplo,\n\nVisita el siguiente enlace para confirmar tu cuenta:\nhttps://tu-dominio.com/verify?token=abc123\n\nEl enlace expira en 24 horas." } ] }' ``` Expected response: ```json { "message": "accepted", "app": "