Documentation
Technical ReferenceREST API

Emails API

Endpoints for email templates, email settings, the sender domain, the setup checklist, the email log and suppressions, with curl examples and MCP tools.

Emails API

The Emails API manages the emails Flo sends your customers: their templates, the switch and settings of each email type, the Reply-To and sender domain, the setup checklist, the email log and the suppression list. The Emails guide explains how these parts fit together.

Every endpoint acts on the authenticated client only. Authenticate with client Basic authentication, using your client identifier (client.uuid) as the username and an active API token as the password, or with an OAuth access token (Authorization: Bearer). In the examples, FLOPAY_BASIC_AUTH holds the Base64 encoding of clientUuid:apiToken. A missing or invalid credential answers 401.

Each endpoint is also a FloPay MCP tool, named under it, except confirming the email review. See Use it from an MCP assistant.

Templates

A template is your own content for one email, keyed by its message type, for example customer.order_confirmation. The {type} in these paths is that message type, as GET /v1/email-templates lists it. An unknown type answers 404. How drafts, previews, test sends and publishing fit together is in Templates in the guide.

List templates

GET /v1/email-templates

Lists every email you can give your own template. The list is paged with page and limit (at most 100, 20 by default), sorts by type, and filters by emailType, enabled (true or false) and source (default or published).

curl "https://api.flopay.com/v1/email-templates?enabled=true" \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "data": [
    {
      "type": "customer.order_confirmation",
      "emailType": "order_confirmation",
      "enabled": true,
      "source": "published",
      "publishedVersion": 2,
      "publishedAt": "2026-11-03T09:12:00.000Z",
      "hasDraft": false
    }
  ],
  "page": 1,
  "limit": 20,
  "pages": 1,
  "total": 1
}
FieldMeaning
typeThe message type, used as {type} in the other template paths.
emailTypeThe email type whose switch controls this email in Settings, or null when no switch controls it.
enabledWhether the email is sent. An email no switch controls is always sent.
sourceWhat is sent: default for Flo's default, published for your published template.
publishedVersion, publishedAtThe template that is sent and when it was published, or null while Flo's default is sent.
hasDraftWhether there is an unpublished draft.

MCP tool: EmailTemplate_list.

List variable schemas

GET /v1/email-templates/schemas

Returns the variable schema of every email type: the variables a template may use, with their types, descriptions, nullability and sample values, the links every template of the type must contain, and a preview sample. GET /v1/email-templates/schemas/{type} returns the schema of one type, for example customer.order_confirmation. The Variables and filters section of the guide explains how to use them.

curl https://api.flopay.com/v1/email-templates/schemas \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
curl https://api.flopay.com/v1/email-templates/schemas/customer.order_confirmation \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"

Each schema has its type, its version, its variables by group (client, customer, items, payment, subscription, checkout_session and links, where items is a list) and a sample for the whole email. Adding a variable keeps the version; removing a deprecated variable raises it by one.

MCP tools: EmailTemplateSchema_list and EmailTemplateSchema_get.

Get a template

GET /v1/email-templates/{type}

Returns your template for one email. Reading a type you never edited stores nothing.

curl https://api.flopay.com/v1/email-templates/customer.order_confirmation \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
FieldMeaning
typeThe message type.
sourceWhat is sent: published for your published template, default for Flo's default.
publishedVersionThe number of the published template that is sent, or null while Flo's default is sent.
publishedThe published template that is sent (mode, its content in body, publishedAt and publishedBy), or null.
draftThe unpublished draft, or null when there is none.
defaultBodyFlo's current default for the email, in structured slots.
lastResetWhen (at) and by whom (by) the template was last reset to the default, or null.

MCP tool: EmailTemplate_get.

Save a draft

PUT /v1/email-templates/{type}/draft

Saves the draft, in structured slots or custom HTML. Nothing that is sent changes until the draft is published. The answer is the template, as Get a template returns it.

For structured slots, send mode set to structured and the slots in content. subject, heading and paragraphs are required. preheader, brandedSubject, highlight, button, buttons, closing and variableDefaults are optional. A text slot is one wording or a list of wordings, each as { "text": "...", "requires": [...] }: the first wording whose variables all have a value is used.

curl -X PUT https://api.flopay.com/v1/email-templates/customer.order_confirmation/draft \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "structured",
    "content": {
      "subject": "Your order from {{ client.name }}",
      "heading": "Thanks for your order",
      "paragraphs": [
        "We received {{ payment.amount | money }}.",
        "Your payment reference is {{ payment.reference }}."
      ]
    }
  }'

For custom HTML, send mode set to html, the subject and the html body.

curl -X PUT https://api.flopay.com/v1/email-templates/customer.order_confirmation/draft \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "html",
    "subject": "Your order from {{ client.name }}",
    "html": "<h1>Thanks for your order</h1><p>We received {{ payment.amount | money }}.</p>"
  }'

Each text is at most 100 KB. A body with an unknown mode, no subject, or a text over 100 KB answers 400.

MCP tool: EmailTemplate_saveDraft.

Preview a template

POST /v1/email-templates/{type}/preview

Renders the draft, or the content currently sent when there is no draft, the way a real email is rendered, with the sample data of the type and your brand. Send source set to sample. A preview changes nothing.

curl -X POST https://api.flopay.com/v1/email-templates/customer.order_confirmation/preview \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "source": "sample" }'

The answer has the rendered subject, html and text, a status and the problems that publishing the same content would refuse. Problems come back in the body, never as an error: status is rendered when the content renders and invalid when it has problems. Each problem has a code (liquid_parse_error, unknown_variable, missing_required_link, render_error or invalid_template), a readable message, and the path, line and column it is about when it has them.

MCP tool: EmailTemplate_preview.

Send a test email

POST /v1/email-templates/{type}/test-send

Sends the draft, or the content currently sent when there is no draft, to one address in to, rendered with the sample data. The subject starts with [Test]. The template and the switches do not change.

curl -X POST https://api.flopay.com/v1/email-templates/customer.order_confirmation/test-send \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "to": "owner@shop.com" }'

A test email that is sent answers 202:

{
  "status": "sent",
  "reason": null,
  "retryAfterSeconds": null
}

Test sends are limited to 10 per rolling hour and 50 per rolling day for your account.

StatusWhen
400to is missing or is not exactly one valid address.
422The content cannot be rendered (not_renderable), or the address is on your suppression list (suppressed).
429The test-send limit is reached. Retry-After and retryAfterSeconds say when to try again.
503Email delivery is not enabled, so no test email can be sent.

MCP tool: EmailTemplate_testSend.

Publish a draft

POST /v1/email-templates/{type}/publish

Validates the draft and publishes it: from then on it is what your customers receive. The answer is the published template, with its mode, its content in body, publishedAt and publishedBy (the user for OAuth calls, the API token for Basic).

curl -X POST https://api.flopay.com/v1/email-templates/customer.order_confirmation/publish \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
StatusWhen
409There is no draft to publish.
422The draft fails validation. reasons lists every problem, and nothing is published.

MCP tool: EmailTemplate_publish.

Reset to the default

POST /v1/email-templates/{type}/reset

Discards the draft and stops sending your published template, so customers receive Flo's default again. Who reset it is recorded in lastReset. The answer is the template, as Get a template returns it.

curl -X POST https://api.flopay.com/v1/email-templates/customer.order_confirmation/reset \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"

MCP tool: EmailTemplate_reset.

Settings

Email settings hold your Reply-To and, for every email type, whether it is sent and its settings. The types and their defaults are explained in Switches and defaults in the guide.

Read email settings

GET /v1/emails/settings

curl https://api.flopay.com/v1/emails/settings \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "replyTo": "support@shop.com",
  "types": [
    {
      "type": "dunning_first_failure",
      "enabled": true,
      "enabledAt": "2026-11-02T10:00:00.000Z",
      "config": {},
      "declarations": []
    },
    {
      "type": "dunning_final",
      "enabled": false,
      "enabledAt": null,
      "config": {},
      "declarations": []
    }
  ]
}
FieldMeaning
replyToYour one Reply-To, the same value as replyTo of the sender settings, or null until you set one.
types[].typeThe email type, for example order_confirmation.
types[].enabledWhether the email is sent. A type you never chose is off.
types[].enabledAtWhen it was last switched on, or null.
types[].configThe settings of the type.
types[].declarationsThe settings the type accepts, each with its key, label, kind (integer, boolean, string or integer_list), default, min, max and help. Empty for a type with no settings.

MCP tool: EmailSettings_get.

Switch an email on or off

PATCH /v1/emails/settings/{type}

Switches one email type on or off with enabled, and saves its settings in config. Send either or both. The {type} is the email type, as GET /v1/emails/settings lists it. The answer is that type.

curl -X PATCH https://api.flopay.com/v1/emails/settings/dunning_final \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'

A config key the type does not declare, a value of the wrong kind, or a value outside the declared min and max answers 400 naming the field, and nothing is stored. Keys you leave out take the type's defaults. An unknown type answers 404.

MCP tool: EmailSettings_updateType.

Set the Reply-To

PATCH /v1/emails/settings

Sets your one Reply-To: one email address of at most 254 characters. It is the same value as replyTo of the sender settings, so setting it here changes it there. The answer is the same as Read email settings.

curl -X PATCH https://api.flopay.com/v1/emails/settings \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "replyTo": "support@shop.com" }'

A stored Reply-To cannot be cleared: null or a blank value answers 400 while one is stored. A list, a display name or anything else that is not one address answers 400, and nothing is stored.

MCP tool: EmailSettings_updateReplyTo.

Sender domain

The sender settings and the delegated sender domain. By default your emails go from no-reply@flopay.com as your brand name "via Flopay", with your Reply-To. A delegated subdomain is the advanced option. How it works, and what the dashboard shows, is in Sender domain in the guide.

Read sender settings

GET /v1/emails/sender/settings

Returns your Reply-To and the From local part used once your sender domain is verified. Before you save settings, replyTo is null and fromLocalPart is billing.

curl https://api.flopay.com/v1/emails/sender/settings \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "replyTo": "support@shop.com",
  "fromLocalPart": "billing"
}

MCP tool: Sender_getSettings.

Update sender settings

PUT /v1/emails/sender/settings

Sets the Reply-To and the From local part. A field you leave out keeps its value.

  • replyTo: one email address of at most 254 characters. It is required for a complete setup and cannot be cleared.
  • fromLocalPart: 1 to 64 lower-case letters, digits, ., _ or -. With the verified sender domain mail.shop.com, billing sends from billing@mail.shop.com.
curl -X PUT https://api.flopay.com/v1/emails/sender/settings \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "replyTo": "support@shop.com", "fromLocalPart": "billing" }'

An invalid value answers 400 with a message naming the field.

MCP tool: Sender_updateSettings.

Add a sender domain

POST /v1/emails/sender/domain

Starts the delegation of one subdomain of your domain (without a scheme or path), with the label mail unless you send another label. Flo hosts the subdomain and writes the records mail needs inside it. The answer is pending, with exactly the four NS records you publish at your DNS host.

curl -X POST https://api.flopay.com/v1/emails/sender/domain \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "shop.com" }'
{
  "name": "mail.shop.com",
  "status": "pending",
  "records": [
    { "type": "NS", "name": "mail.shop.com", "value": "ns1.dns.flopay.com" },
    { "type": "NS", "name": "mail.shop.com", "value": "ns2.dns.flopay.com" },
    { "type": "NS", "name": "mail.shop.com", "value": "ns3.dns.flopay.com" },
    { "type": "NS", "name": "mail.shop.com", "value": "ns4.dns.flopay.com" }
  ],
  "verifiedAt": null,
  "brokenReason": null
}

These four NS records at mail.<client-domain> are the only records you publish. Nothing changes at your root domain.

StatusWhen
400The domain or label is malformed, or is a Flopay domain.
409You already have a sender domain, or the subdomain is in use.

MCP tool: Sender_addDomain.

Read the sender domain

GET /v1/emails/sender/domain

Returns your sender domain in the same shape as Add a sender domain, with its current status: pending (the delegation is started and not verified yet), verified (mail goes from the domain) or broken (it was verified and now fails, with the reason in brokenReason). Without a sender domain it answers 404.

curl https://api.flopay.com/v1/emails/sender/domain \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"

MCP tool: Sender_getDomain.

Verify the sender domain

POST /v1/emails/sender/domain/verify

Checks each NS record of the delegation and the records Flo wrote in the subdomain. When every check passes, the domain is verified and new mail goes from it. Otherwise failing names the checks that failed. A DNS lookup that times out reads unknown and changes nothing. Without a sender domain it answers 404.

curl -X POST https://api.flopay.com/v1/emails/sender/domain/verify \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "status": "verified",
  "nsRecords": [
    { "name": "mail.shop.com", "value": "ns1.dns.flopay.com", "check": "found" },
    { "name": "mail.shop.com", "value": "ns2.dns.flopay.com", "check": "found" },
    { "name": "mail.shop.com", "value": "ns3.dns.flopay.com", "check": "found" },
    { "name": "mail.shop.com", "value": "ns4.dns.flopay.com", "check": "found" }
  ],
  "dkim": "found",
  "returnPath": "found",
  "failing": [],
  "verifiedAt": "2026-11-04T10:00:00.000Z"
}

Each check reads found, missing or unknown. nsRecords has one entry for each of the four NS records you publish. dkim and returnPath check the two records Flo writes in the subdomain itself: you never add them.

MCP tool: Sender_verifyDomain.

Remove the sender domain

DELETE /v1/emails/sender/domain

Removes the sender domain. Mail goes from no-reply@flopay.com again at once. Flo keeps the empty subdomain until you take the four NS records off your DNS host. Answers 204, or 404 without a sender domain.

curl -X DELETE https://api.flopay.com/v1/emails/sender/domain \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"

MCP tool: Sender_removeDomain.

Setup checklist

The setup steps of your account, in the order to show them. Render the list as data: a step added later appears without a dashboard release, so do not depend on its keys or length.

Read the checklist

GET /v1/setup-checklist

curl https://api.flopay.com/v1/setup-checklist \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "complete": false,
  "steps": [
    { "key": "brand_set", "title": "Set up your brand", "done": true, "optional": false },
    { "key": "gateway_connected", "title": "Connect a gateway", "done": true, "optional": false },
    { "key": "reply_to_set", "title": "Set your Reply-To", "done": true, "optional": false },
    { "key": "sender_domain_verified", "title": "Verify your sender domain", "done": false, "optional": true },
    { "key": "emails_reviewed", "title": "Review your emails", "done": false, "optional": false }
  ]
}
FieldMeaning
completetrue while every step that is not optional is done.
steps[].keyThe step, stable across releases: brand_set, gateway_connected, reply_to_set, sender_domain_verified or emails_reviewed.
steps[].titleWhat to do, ready to show. The titles in the example are illustrative: show the text the API returns.
steps[].doneWhether the step is done. Brand, gateway, Reply-To and sender domain follow their current state on every read, so a sender domain that breaks after verification reads not done again.
steps[].optionalWhether the step is optional. Only sender_domain_verified is optional: it reports its real state but never holds back complete.

MCP tool: SetupChecklist_get.

Confirm the email review

POST /v1/setup-checklist/review

Records that you reviewed your emails, with when and by which user, and answers the checklist. Confirming again succeeds and keeps the first confirmation. Changing switches, settings or the Reply-To never counts as the review.

curl -X POST https://api.flopay.com/v1/setup-checklist/review \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"

Confirming the review is your own act, so it is REST only and has no MCP tool.

Email log

GET /v1/emails/log

Every email Flo sent or tried to send for you, newest first. The list is paged with page and limit (at most 100), sorts by createdAt or updatedAt, and filters by status, type, recipient, linkedResourceType, linkedResourceId and createdAt (with [gte], [gt], [lte] or [lt] for a range). status, type, recipient, linkedResourceType and linkedResourceId also accept [in] with a comma-separated list. The email log is read-only. What each status means is in Email log and suppression in the guide.

Every email about one payment:

curl "https://api.flopay.com/v1/emails/log?linkedResourceType=payment&linkedResourceId=7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"

Every email that bounced or drew a complaint:

curl "https://api.flopay.com/v1/emails/log?status[in]=bounced,complained" \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "data": [
    {
      "id": "2f1d0c4e-8b7a-4d3c-9e6f-5a4b3c2d1e0f",
      "clientId": "4f1c2b7e-9a3d-4e8f-b2c6-1d5a7e9f0c3b",
      "outboxMessageId": "b8e2a6d4-1c3f-4e5a-9b7d-0f2e4c6a8b1d",
      "messageType": "customer.order_confirmation",
      "recipientEmail": "ada@example.com",
      "status": "delivered",
      "skipReason": null,
      "bounceType": null,
      "providerMessageId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "linkedResourceType": "payment",
      "linkedResourceId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "sentAt": "2026-11-02T10:00:04.000Z",
      "createdAt": "2026-11-02T10:00:02.000Z",
      "updatedAt": "2026-11-02T10:00:09.000Z"
    }
  ],
  "page": 1,
  "limit": 20,
  "pages": 1,
  "total": 1
}
FieldMeaning
statusThe delivery status: queued, sent, delivered, bounced, complained, suppressed or skipped.
skipReasonWhy the email ended as skipped, otherwise null.
bounceTypeThe bounce type once the email bounced, otherwise null.
linkedResourceType, linkedResourceIdThe payment, subscription or checkout_session the email is about, when known.
sentAtWhen the email provider accepted the email, otherwise null.
outboxMessageIdOne entry per message, across retries.

An invalid query answers 400.

MCP tool: EmailLog_listLog.

Suppression list

GET /v1/emails/suppressions

The addresses Flo no longer sends to for you, after a hard bounce or a spam complaint. The list is paged with page and limit, sorts by createdAt, and filters by recipient, reason (hard_bounce or complaint) and createdAt. The suppression list is read-only: no endpoint adds or removes an address.

curl "https://api.flopay.com/v1/emails/suppressions?reason=hard_bounce" \
  -H "Authorization: Basic $FLOPAY_BASIC_AUTH"
{
  "data": [
    {
      "id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
      "clientId": "4f1c2b7e-9a3d-4e8f-b2c6-1d5a7e9f0c3b",
      "email": "old-address@example.com",
      "reason": "hard_bounce",
      "sourceEntryId": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
      "createdAt": "2026-11-03T08:30:00.000Z"
    }
  ],
  "page": 1,
  "limit": 20,
  "pages": 1,
  "total": 1
}

sourceEntryId is the email log entry whose bounce or complaint suppressed the address, when known. An invalid query answers 400.

MCP tool: EmailLog_listSuppressions.

Use it from an MCP assistant

Every endpoint on this page except POST /v1/setup-checklist/review is a FloPay MCP tool, named under the endpoint. Reads are read-only tools for owners, admins and members. Changes are actions for owners and admins: the assistant shows you a preview and runs the action only after you confirm it. Adding, verifying and removing a sender domain and changing the sender settings are high-risk actions. The tool reference lists the arguments, results and policy of every tool.

On this page