Mailement
About Pricing Help Sign in Start an account
API reference
  1. 1Overview
  2. 2Authentication
  3. 3Errors & responses
  4. 4POST /api/v1/subscribe
  5. 5POST /api/v1/subscribers
  6. 6POST /api/v1/subscribers/unsubscribe
  7. 7POST /api/v1/subscribers/tags
  8. 8POST /api/v1/drips/{id}/enroll
  9. 9Outbound webhooks
  10. 10CORS
  11. 11Rate limits
  12. 12Versioning
Docs · API reference

Mailement API.

Last updated June 6, 2026 Version v1 Base URL https://mailement.com
Mailement exposes a small JSON HTTP API for subscribe, unsubscribe, tag, and drip enrollment. Auth is per-brand bearer tokens. CORS is permissive on subscribe endpoints so you can POST directly from a brand's website. The full surface fits on this page.

01Overview

The API is designed for two scenarios. First, brand-side custom signup forms that want to skip Mailement's hosted form or iframe and POST directly. Second, server-to-server integration with a brand's own CRM, e-commerce platform, or course tool — to subscribe customers, tag them, opt them out, and enroll them in drip series.

All endpoints accept JSON request bodies. All responses are JSON. The base URL is https://mailement.com. Every request must include a brand-scoped bearer token (see Authentication).

02Authentication

Generate API keys at /admin/api-keys while signed in. Each key is scoped to a single brand — the key encodes which brand it can operate on. Token format:

mlm_live_<32 hex characters>

Pass the token in the Authorization header on every request:

Authorization: Bearer mlm_live_a1b2c3d4e5f6...

The "tag-flavored" endpoints (/api/v1/subscribers*) also accept the token via X-Newsletter-Api-Key for compatibility with form integrations that prefer a custom header. Either works on those endpoints; Authorization: Bearer works everywhere.

Keys are stored as sha256 hashes server-side. The plain key is shown once at creation time and never again — copy it then. If you lose a key, revoke it from the admin and issue a new one.

03Errors & responses

Successful responses include either "ok": true or "success": true depending on the endpoint family. The /api/v1/subscribe and /api/v1/drips/{id}/enroll endpoints use ok. The tag-flavored /api/v1/subscribers* endpoints use success. Both shapes also return relevant ids on success.

Errors return a 4xx or 5xx status with a short error code string. Codes you may see:

  • missing_api_key (401) — Authorization header absent.
  • invalid_api_key (401) — token doesn't match a known key.
  • brand_not_found (404) — the brand backing the key has been deleted.
  • invalid_email (422) — failed RFC 5322 validation.
  • email_too_long, first_name_too_long, last_name_too_long, city_too_long (422) — exceeded the column limits below.
  • missing_subscriber_identifier (422) — no email or subscriber_id in the body (drip-enroll, unsubscribe, tags).
  • subscriber_not_found (404) — that subscriber doesn't exist for this brand.
  • drip_not_found (404) — the drip id isn't owned by this brand.
  • enroll_failed (422) — drip enrollment couldn't proceed; check the reason field.

Length limits enforced server-side: email 190 chars, first name 80, last name 80, city 120. Custom fields are trimmed to the brand's defined keys; unknown keys in the request body are silently dropped, which prevents the API from being used to write arbitrary JSON into a subscriber row.

04POST /api/v1/subscribe

Topic-id-flavored subscribe. Idempotent on (brand, email). Use this when your form knows the numeric topic ids it wants to subscribe to.

Request:

POST /api/v1/subscribe
Authorization: Bearer mlm_live_...
Content-Type: application/json

{
  "email": "reader@example.com",
  "first_name": "Maria",
  "last_name":  "Costa",
  "city":       "Lisbon",
  "topic_ids":  [12, 17],
  "custom_fields": {
    "favorite_genre": "essays",
    "referral_source": "twitter"
  }
}

Behavior: finds or creates a subscriber for the brand. On a new row, the response status depends on the brand's opt_in_mode — "confirmed" for single opt-in, "pending" for double opt-in. Double-opt-in API signups do not auto-send a confirmation email (your form may have its own flow); admins can resend manually from /admin/subscribers if needed.

If the email already exists: missing names/city are backfilled (existing values are preserved with COALESCE). Unsubscribed subscribers are reactivated according to opt-in mode. Bounced or complained subscribers are returned as-is without modification.

Topic IDs are filtered to public-visible topics for this brand — IDs that don't belong or aren't public are dropped. Passing topic_ids at all replaces existing memberships (passing [] clears them); omitting the key leaves existing topics alone.

For single-opt-in signups, drip enrollment fires automatically after topic sync — any drip with a subscriber_confirmed or matching topic_added trigger will start.

Response (200):

{
  "ok": true,
  "subscriber_id": 41023,
  "status": "pending"
}

05POST /api/v1/subscribers

Tag-flavored subscribe. Use this if your existing integration code is written for tools that talk in tags rather than numeric ids (Mailchimp, Kit, Buttondown patterns). Idempotent on (brand, email).

Request:

POST /api/v1/subscribers
Authorization: Bearer mlm_live_...
Content-Type: application/json

{
  "email":      "reader@example.com",
  "first_name": "Maria",
  "source":     "homepage_form",
  "tags":       ["weekly-digest", "deep-dives"],
  "auto_confirm": false
}

Behavior: same find-or-create logic as /api/v1/subscribe but accepts tags as an array of topic slugs scoped to the brand. Unknown slugs are silently dropped and logged via error_log so admins can audit typos.

auto_confirm (boolean) is a trusted-source flag: when true, the request asserts the email is already verified (e.g., dashboard re-subscribe by a brand-authenticated user) and bypasses double opt-in for this request only. The brand's stored opt-in mode is not changed.

Response statuses returned in the body's status field:

  • "subscribed" — new row, single-opt-in (confirmed immediately).
  • "pending_confirmation" — new row, double-opt-in (awaiting click).
  • "existing" — email already known to this brand (idempotent re-call).

Response (200):

{
  "success": true,
  "subscriber_id": 41023,
  "status": "pending_confirmation"
}

06POST /api/v1/subscribers/unsubscribe

Opt a subscriber out. Identify by email (preferred) or subscriber_id.

POST /api/v1/subscribers/unsubscribe
Authorization: Bearer mlm_live_...
Content-Type: application/json

{ "email": "reader@example.com" }

Only flips the status on the actual transition — repeat calls are no-ops. Fires subscriber.unsubscribed outbound webhook on the first call (see Outbound webhooks).

Response (200):

{ "success": true, "subscriber_id": 41023 }

07POST /api/v1/subscribers/tags

Add or remove topic memberships by slug. Identify the subscriber by email or subscriber_id.

POST /api/v1/subscribers/tags
Authorization: Bearer mlm_live_...
Content-Type: application/json

{
  "email":  "reader@example.com",
  "add":    ["weekly-digest"],
  "remove": ["deep-dives"]
}

Slugs are resolved to topic ids scoped to the API key's brand. Unknown slugs are dropped. The response echoes which ids were actually added and removed so you can verify the mapping.

Response (200):

{
  "success": true,
  "subscriber_id": 41023,
  "added":   [12],
  "removed": [17]
}

Note: this endpoint does not trigger drip enrollment, because v1's topic_added trigger only fires at signup time. Use /api/v1/drips/{id}/enroll if you want to start a drip on a tag change.

08POST /api/v1/drips/{id}/enroll

Enroll a subscriber in a drip series. Works on any drip in the API key's brand, regardless of its configured trigger type. Idempotent on (drip, subscriber).

POST /api/v1/drips/42/enroll
Authorization: Bearer mlm_live_...
Content-Type: application/json

{ "email": "reader@example.com" }

Identify the subscriber by email or subscriber_id. The drip must belong to the API key's brand — cross-brand enrollment returns drip_not_found even if the IDs would otherwise resolve.

Steps begin firing on the next cron tick (drip cron runs every 10 minutes). The first step's delay is honored relative to enrollment time.

Response (200):

{
  "ok": true,
  "subscriber_id": 41023,
  "drip_id": 42,
  "already_enrolled": false
}

If the subscriber was already in this drip, already_enrolled is true and no new run is created.

09Outbound webhooks

Configure one webhook URL per brand at /admin/webhooks. Mailement POSTs a signed JSON payload on these events:

  • subscriber.confirmed — double-opt-in confirm or single-opt-in signup.
  • subscriber.unsubscribed — one-click unsubscribe or API unsubscribe.
  • subscriber.bounced — Resend reports the address as bounced.
  • subscriber.complained — Resend reports the recipient marked the mail as spam.

Payload:

POST <your URL>
Content-Type: application/json
User-Agent: Mailement-Webhook/1.0
X-Mailement-Event: subscriber.confirmed
X-Mailement-Signature: sha256=<hmac>

{
  "event":      "subscriber.confirmed",
  "brand_slug": "nowness",
  "timestamp":  "2026-05-19T17:42:00+00:00",
  "data": {
    "subscriber": {
      "id":              41023,
      "email":           "reader@example.com",
      "first_name":      "Maria",
      "last_name":       "Costa",
      "city":            "Lisbon",
      "custom_fields":   { "favorite_genre": "essays" },
      "status":          "confirmed",
      "source":          "api",
      "confirmed_at":    "2026-05-19 17:42:00",
      "unsubscribed_at": null
    }
  }
}

Signature verification: the X-Mailement-Signature header is sha256=<hex hmac> computed over the raw request body using your brand's webhook secret (shown in the admin when you set up the webhook). Compute the same HMAC on your end and constant-time compare. Sample (PHP):

$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $brandSecret);
if (!hash_equals($expected, $signatureHeader)) {
    http_response_code(401);
    exit;
}

Delivery: synchronous with a tight timeout (3s connect, 5s total). Mailement does not retry in v1 — respond 2xx within the window. Failed deliveries are recorded on the brand row and visible in /admin/webhooks.

10CORS

The subscribe and drip-enroll endpoints respond to OPTIONS with permissive CORS headers (Access-Control-Allow-Origin: *) so a brand's own website can POST directly from JavaScript in the browser. The legitimate use case is cross-origin by design — your form lives at example.com and posts to mailement.com.

Abuse is bounded by the secret bearer token. A leaked key only affects one brand, and you can revoke and re-issue from /admin/api-keys at any time.

11Rate limits

V1 has no published per-second rate limit. Server-side protections exist to prevent abuse (length caps, custom-field whitelisting, brand scoping) but volume isn't currently throttled. If you're planning a bulk operation — importing thousands of subscribers via API, for example — use the CSV import at /admin/subscribers/import instead, which is purpose-built for that and bypasses the per-row overhead.

If your integration drives sustained high volume, write to hello@mail.mailement.com and we'll talk about a dedicated path.

12Versioning

All endpoints live under /api/v1/. Breaking changes will ship under a new prefix (/api/v2/) rather than mutating v1 responses. Additive changes — new optional request fields, new response fields — may land in v1 without bumping the version.

Endpoints planned for future versions but not available today: read endpoints for drip runs and per-mail analytics, an export endpoint, a webhook resend endpoint. Don't write integration code against these yet.

{}
Building something with the API?
We love seeing integrations. Email hello@mail.mailement.com — if you hit something missing or broken, we'll usually ship a fix the same week.
Mailement · © 2026 · Sent with care.
About · Help · FAQ · Migrate · API · Terms · Privacy