Kestra setup
Connect SendBeam to Kestra: what you need, the steps, and the code.
Via API
Kestra describes a pipeline as YAML — inputs, tasks, triggers — and runs it on a schedule or on an event. SendBeam is a JSON API behind one header, so a flow that adds a customer or sends an order confirmation is an HTTP task with a secret, and SendBeam's webhooks can start a flow the same way.
Before you start
- A Kestra instance (0.18 or later)
- A SendBeam API key with the permissions the flow needs
- A verified sending domain, for site email
Set it up
- Create an API key. Settings → API keys in SendBeam, with
contacts:writeandtransactional:send(andlists:writeif you add a list task:POST /api/v1/lists/{id}/contactswith the same headers). The full key is shown once. Store it as the Kestra secretSENDBEAM_API_KEY(base64-encoded in theSECRET_SENDBEAM_API_KEYenvironment variable on a self-hosted instance). - Add the flow. Paste the YAML below into a new flow. Its inputs become the form you fill in when running it by hand.
- Run it once with your own address, check the contact and the email in SendBeam, then add a schedule or a trigger.
- Optional: let SendBeam start a flow. The second file below has a webhook trigger; add its URL under Settings → Webhooks in SendBeam.
Code
The flow: add a contact and welcome them
sendbeam-welcome.yaml
id: sendbeam_welcome
namespace: company.marketing
inputs:
- id: email
type: STRING
- id: name
type: STRING
defaults: ""
tasks:
- id: add_contact
type: io.kestra.plugin.core.http.Request
uri: https://sendbeam.io/api/v1/contacts
method: POST
contentType: application/json
headers:
x-api-key: "{{ secret('SENDBEAM_API_KEY') }}"
body: |
{
"email": "{{ inputs.email }}",
"first_name": "{{ inputs.name | split(' ') | first }}",
"source": "kestra"
}
# 409 means the address is already a contact: carry on.
allowFailed: true
- id: welcome_email
type: io.kestra.plugin.core.http.Request
uri: https://sendbeam.io/api/v1/transactional
method: POST
contentType: application/json
headers:
x-api-key: "{{ secret('SENDBEAM_API_KEY') }}"
body: |
{
"to": { "email": "{{ inputs.email }}", "name": "{{ inputs.name }}" },
"subject": "Welcome aboard",
"html": "<p>Hi {{ inputs.name | split(' ') | first | default('there') }}, thanks for signing up.</p>"
}
The trigger: a SendBeam event starts a flow
sendbeam-events.yaml
id: sendbeam_events
namespace: company.marketing
triggers:
- id: sendbeam
type: io.kestra.plugin.core.trigger.Webhook
# Kestra's webhook URL for this flow ends in the namespace, the flow id and this key.
key: "{{ secret('SENDBEAM_WEBHOOK_KEY') }}"
tasks:
- id: route
type: io.kestra.plugin.core.flow.Switch
value: "{{ trigger.body.type }}"
cases:
contact.created:
- id: on_contact
type: io.kestra.plugin.core.log.Log
message: "New contact {{ trigger.body.data.email }}"
email.clicked:
- id: on_click
type: io.kestra.plugin.core.log.Log
message: "Click from {{ trigger.body.data.email }}"
defaults:
- id: ignore
type: io.kestra.plugin.core.log.Log
message: "Ignored {{ trigger.body.type }}"
The webhook key in the URL is what authenticates SendBeam to Kestra; keep it as a secret and rotate it from Settings → Webhooks if it leaks. For signature verification as well, put a small script task (Python or Node) before route that checks X-SendBeam-Signature the way the Webhooks docs show.
Things to know
- Use one secret per environment so a development instance never writes to your production list.
POST /api/v1/contactsanswers 409 for an address that already exists;allowFailed: truelets the flow carry on, which makes a re-run safe.- Kestra’s
Scheduletrigger turns the first flow into a nightly sync when its inputs come from a query or a file task earlier in the flow. - Only add people to a marketing list if they agreed to hear from you. Site email through
/api/v1/transactionalneeds no marketing consent.