Skip to content

Send a transactional email

This tutorial sends a transactional email with the BoostMail API, from minting a key to handling the send receipt. You mint a key, set it as an environment variable, create a subscriber to send to, send the transactional email, and branch on the response.

Before you start, you need:

  • An API key minted with the subscribers:write and transactional:send scopes. Mint one in the BoostMail dashboard: see the Authentication guide for the key format and the full scope list.
  • A transactional campaign set up in your BoostMail dashboard, under Campaigns. No /v1 endpoint creates one, so you set it up in the dashboard first. Note its id: this tutorial uses the placeholder <your-transactional-campaign-id> for it.

Set your key as an environment variable:

Terminal window
export BOOSTMAIL_API_KEY=bm_live_...
  1. Mint a key with the subscribers:write and transactional:send scopes in the BoostMail dashboard. BoostMail shows the secret once, so copy it before you navigate away.

  2. Set the key in your shell:

    Terminal window
    export BOOSTMAIL_API_KEY=bm_live_...
  3. Create a subscriber to send to. Transactional send does not require the recipient to already be a subscriber (see step 4), but this gives you a known recipient to test against.

    Terminal window
    curl -X POST https://api.boostmail.app/v1/subscribers \
    -H "Authorization: Bearer $BOOSTMAIL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"email": "[email protected]"}'

    If you run this again with the same email, BoostMail returns 422 validation_failed instead of creating a duplicate:

    {
    "error": {
    "code": "validation_failed",
    "message": "validation_failed",
    "details": [
    {"field": "email", "code": "already_exists", "message": "A subscriber with this email already exists"}
    ]
    }
    }

    This is expected, not a bug. Use a fresh email for a clean run, or treat the 422 as confirmation the subscriber is already there.

  4. Send the transactional email. The call requires an idempotency-key header: generate a fresh value (a UUID works well) for each new logical send, and reuse that same value on every retry of it. Use the campaign_id of the transactional campaign from the Prerequisites.

    Terminal window
    curl -X POST https://api.boostmail.app/v1/transactional/send \
    -H "Authorization: Bearer $BOOSTMAIL_API_KEY" \
    -H "Content-Type: application/json" \
    -H "idempotency-key: <uuid>" \
    -d '{
    "campaign_id": "<your-transactional-campaign-id>",
    "email": "[email protected]"
    }'

    You can also pass an optional template_data object with any merge fields your transactional campaign template uses. It is part of the idempotency fingerprint: reusing the same idempotency-key with different template_data changes the fingerprint and returns 409 conflict (idempotency-key reuse), it does not send a second message. To send a genuinely different message, use a new idempotency-key.

  5. Handle the receipt. A 200 response always carries a status field: branch on it before reading anything else.

    A non-suppressed send returns:

    {
    "status": "scheduled",
    "message_id": "<id>",
    "scheduled_at": "<iso-8601>"
    }

    A suppressed send returns:

    {
    "status": "suppressed",
    "reason": "hard_bounce"
    }

    reason is hard_bounce or spam_complaint. A suppressed receipt means BoostMail accepted the request but enqueued nothing: no message_id or scheduled_at on that branch. See the Suppression guide for what causes a recipient to be suppressed.