Documentation

Send a message from your server

Send one email, SMS, WhatsApp message, or push to one contact with a single request and a secret key.

Send a receipt, a password reset, or any message your backend decides on. One request sends one message to one contact, through a channel connection you already set up. About 5 minutes.

Before you start

You need an active connection for the channel: email, SMS, WhatsApp, or push. You also need a secret key that can send messages. In FounderHQ, open Settings → API Keys, choose Send messages from your server, and store the key in your server environment.

Nothing is sent anywhere. A publishable key is saved in this browser. A secret key is kept only until you reload.

Send with the content in the request

Put the whole message in content. You do not need a template.

curl https://app.getfounderhq.com/api/v1/messages \
  -H "Authorization: Bearer fhq_sk_XXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reset-user_42-2026-10-04" \
  -d '{
    "channel": "email",
    "to": { "email": "jane@acme.com" },
    "content": {
      "subject": "Reset your password",
      "html": "<p>Hi {{ contact.firstName | default: \"there\" }}, <a href=\"{{ vars.resetLink }}\">reset your password</a>.</p>"
    },
    "variables": { "resetLink": "https://app.acme.com/reset/abc123" }
  }'

FounderHQ answers 202 with the message. queued means FounderHQ accepted it and sends it within moments.

{
  "id": "0f8b3a52-6d0e-4c0e-9a55-0f6a2b1c7d11",
  "channel": "email",
  "status": "queued",
  "category": "transactional",
  "contactId": "c5e1a2f0-93b4-4a61-8d1e-7b9f4c2d6e10",
  "to": "jane@acme.com",
  "connection": "7a1c9d2e-4b3f-4e8a-b6c5-2d1e0f9a8b7c",
  "template": null,
  "subject": "Reset your password",
  "error": null,
  "createdAt": "2026-10-04T10:00:00.000Z",
  "sentAt": null,
  "deliveredAt": null,
  "openedAt": null,
  "clickedAt": null,
  "bouncedAt": null
}

Send with a template

Pass the template's id in template. FounderHQ sends its published version and takes the channel from the template.

curl https://app.getfounderhq.com/api/v1/messages \
  -H "Authorization: Bearer fhq_sk_XXXX" \
  -H "Content-Type: application/json" \
  -d '{
    "to": { "externalId": "user_42" },
    "template": "TEMPLATE_ID",
    "variables": { "orderNumber": "1042" }
  }'

List your templates to find the id:

curl "https://app.getfounderhq.com/api/v1/templates?channel=email" \
  -H "Authorization: Bearer fhq_sk_XXXX"

Request fields

FieldRequiredWhat it does
channelWith contentemail, sms, whatsapp, or push
toYesThe contact. See Who gets the message
contentOne of the twoThe message itself. See Content for each channel
templateOne of the twoThe id of a published template
variablesNoValues the message reads as {{ vars.name }}
connectionNoThe id of the connection that sends. See Choose the connection
categoryNotransactional (default) or marketing

Send content or template, never both.

Who gets the message

to identifies one contact. Use one of these two forms:

  • externalId, email, phone, or several of them. FounderHQ finds the contact, or creates one in the brand of your key. An email or phone the contact did not have is saved on the contact.
  • contactId alone: the id of a contact FounderHQ already has.

The message goes to the contact's saved address: the email for email, the phone number for SMS and WhatsApp. Push goes to every device the contact registered. If your only push connection is a custom HTTP integration, FounderHQ sends it one request, with the device the contact used last.

email is one bare address, like jane@acme.com. FounderHQ refuses a list of addresses and a name with an address.

If externalId and email belong to two different contacts, FounderHQ answers 409 and sends nothing.

Content for each channel

Email

FieldRequiredNotes
subjectYes
htmlYes
textNoThe plain-text version
fromEmailNoMust be on a domain your connection has verified, or FounderHQ answers 422. Default: the connection's sender
fromNameNo
replyToNo
headersNoUp to 20 custom headers. Not To, Cc, Bcc, From, Reply-To, Subject, or other headers FounderHQ sets

SMS

FieldRequiredNotes
textYes
senderId, dltTemplateId, smsTypeNoFor India. See India DLT

Push

FieldRequiredNotes
titleYes
bodyYes
linkNoA URL or deep link the app opens on tap
imageUrlNo
notificationKeyNoThe name of the notification on the device, such as order-1042. A later push with the same key replaces it, and a removal names it. What it renders to is 64 bytes at most. With none, the message ID is used. See Replace or remove a notification
dataNoUp to 50 string values for your app

WhatsApp

WhatsApp accepts free text only within 24 hours of the contact's last message to you. At any other time, send a template that WhatsApp approved.

FieldRequiredNotes
templateNameOne of the twoThe approved template's name in WhatsApp
languageCodeNoDefault en_US
headerParameters, bodyParametersNoLists of { "value": "..." }, in the template's order. Add "name" for a named parameter
buttonParametersNoLists of { "index": "0", "parameters": [{ "value": "..." }] }
textOne of the twoFree text, inside the 24-hour window
{
  "channel": "whatsapp",
  "to": { "phone": "+12125550123" },
  "content": {
    "templateName": "order_shipped",
    "languageCode": "en",
    "bodyParameters": [{ "value": "Jane" }, { "value": "1042" }]
  }
}

This works on a Meta WhatsApp connection. On a Twilio connection, a template with parameters must be imported in FounderHQ first: send it with template, not with content.

Remove a push notification

POST https://app.getfounderhq.com/api/v1/push/remove takes a notification off a person's phones and tablets. Use the same secret key.

Name the notification in one of two ways:

{ "messageId": "9f0c1c0e-6a51-4a6e-9a7f-2b9d6f0f1a11" }
{ "to": { "externalId": "user_42" }, "notificationKey": "order-1042" }
FieldNotes
messageIdThe id of a push this API sent. Its person and its notification key are used
to{ "contactId": "..." } or { "externalId": "..." }. The contact must exist; none is created
notificationKeyWith to. The key the push was sent with, 64 bytes at most

The answer says how many devices were asked:

{ "notificationKey": "order-1042", "devices": 2, "accepted": 2, "failed": 0 }

accepted counts the devices whose push service took the request. It does not mean the notification is gone: the app removes it when the silent push arrives. A person with no app device answers with devices: 0. Browsers are not counted; a page removes its own notification. Your app needs the SDK setup under Remove from your server.

Personalize the content

Every text field in content is a Liquid template, the same as a template you build in FounderHQ. Use {{ contact.firstName }}, {{ contact.email }}, {{ brand.name }}, and {{ vars.name }} for a value from variables.

FounderHQ refuses the request with 422 when the content uses a variable that has no value. Add a default to make one optional: {{ contact.firstName | default: "there" }}.

If your own text contains {{, write it as {{ "{{" }}. FounderHQ then sends the two braces as they are.

FounderHQ refuses content that takes more than 2 seconds to render, or that renders to more than 2 MB.

Choose the connection

Leave connection out and FounderHQ sends through the brand's default connection for the channel. Push goes through every push connection.

To send through a specific one, pass its id. In FounderHQ, open Sequences settings, open the connection's menu, and choose Copy ID. Or list the active connections of your key's brand:

curl "https://app.getfounderhq.com/api/v1/connections?channel=email" \
  -H "Authorization: Bearer fhq_sk_XXXX"
{
  "connections": [
    {
      "id": "7a1c9d2e-4b3f-4e8a-b6c5-2d1e0f9a8b7c",
      "name": "Acme transactional",
      "channel": "email",
      "provider": "resend",
      "isDefault": true
    }
  ]
}

For each channel, the first connection in the list is the one FounderHQ uses when you leave connection out.

Transactional and marketing

A message is transactional unless you send "category": "marketing".

  • Transactional: for messages the contact expects, like a receipt. FounderHQ sends it at once, also to a contact who unsubscribed from marketing email.
  • Marketing: FounderHQ checks that the contact did not unsubscribe, adds the unsubscribe header to email, and holds the message during your quiet hours.

Both skip an address that bounced or complained, and a contact who opted out of SMS or WhatsApp.

Add an unsubscribe link to a marketing email with {{ system.unsubscribeUrl }}.

Send safely more than once

Send an Idempotency-Key header with a value that is unique to the message, such as receipt-order-1042. If your server retries the request with the same key, FounderHQ answers 200 with the first message and sends nothing new.

A key is remembered for as long as the message is in your log, and FounderHQ does not compare the two requests. Use a new key for every new message.

Verify

Ask for the message by its id:

curl https://app.getfounderhq.com/api/v1/messages/MESSAGE_ID \
  -H "Authorization: Bearer fhq_sk_XXXX"

status moves from queued to sent, then to delivered when the provider confirms it. In FounderHQ, open Sequences → Message log. The message is in the log with the source API.

StatusMeaning
queued, sendingOn its way
sentThe provider accepted it
deliveredThe provider confirmed delivery
bouncedThe address refused it
failedFounderHQ could not send it. error says why
suppressedNot sent: the contact unsubscribed, or the address is blocked. error says which
skippedNot sent: the contact was deleted before FounderHQ sent it

For a push, to is null: the message goes to the contact's devices.

Troubleshoot

403 "API key type is not allowed here"

You sent a publishable key (fhq_pk_). Use a secret key (fhq_sk_).

401 or 403 with a secret key

The key cannot send messages. Create a key with Send messages from your server. That key can also create and update contacts, which a send needs.

422 "No active email connection"

The brand of your key has no active connection for this channel. Connect one, or finish its setup, in Sequences settings.

422 "Publish this template before sending it."

The template has no published version. Open it in FounderHQ and publish it.

422 "This person has no device that can get a push."

The contact has not registered a device. See Register a device.

400 "Attachments are not supported yet."

The API does not send attachments. Put a link to the file in the message.

AI agent or LLM? Read this page as markdown

On this page