Send email, make voice calls and run multi-step customer journeys, for example an SMS, then an email, then a call if there’s no response, by submitting your customers’ details to a Workflow instance.
POST one or more records to https://wfa.everymessage.com/rest/v1/records/submit. Each record is a contact plus any data the message needs. The instance you name decides what happens to each record. Keep the workflowRecordId we return, and use /records/query to see the record’s progress and the SMS and email it sent.
How it works
- Instance: a journey we set up with you, made of actions such as send an email, send an SMS, make a voice call, wait, or check a reply. You refer to it by name, for example
payment_reminder. - Record: one contact, identified by your own
primaryReference, with their phone number, email address, name and address. - Variables: extra values for this record, such as a balance or an appointment time. Their names match the placeholders in the instance’s messages.
The Workflow API runs on its own address, wfa.everymessage.com, and uses its own API key and secret with HTTP Basic authentication (key as the username, secret as the password). Contact us to set up an instance and get your keys. See also business email and AI voice assistants.
To have a journey call your own systems, for live data lookups, SMS replies and delivery receipts, see Workflow webhooks.
Always check responseStatusCode. The API returns HTTP 200 even when a request is refused. The real result is responseStatusCode in the response body: 200 means success; anything else comes with a responseStatusReason and a responseStatusMessage.
{
"apiName": "everymessage Workflow Web API",
"apiVersion": "1.1.2",
"apiTime": "11 October 2026 13:53:41",
"apiState": "RUNNING",
"responseStatusCode": 401,
"responseStatusReason": "UNAUTHORIZED",
"responseStatusMessage": "Access denied.",
"processingTime": 16
}
Submit records
https://wfa.everymessage.com/rest/v1/records/submitRecords are stored and processed straight away, or at the scheduled time if you set one.
Request
Email and SMS reminder
{
"instanceName": "payment_reminder",
"records": [
{
"primaryReference": "CUST-1042",
"firstName": "Sam",
"phoneNumber": "07700900123",
"emailAddress": "sam@example.com",
"variables": [
{
"name": "balance",
"value": "345.22"
},
{
"name": "due_date",
"value": "16/11/2026"
}
]
}
]
}Several records
{
"instanceName": "payment_reminder",
"batchName": "November reminders",
"records": [
{
"primaryReference": "CUST-1042",
"firstName": "Sam",
"phoneNumber": "07700900123",
"emailAddress": "sam@example.com",
"variables": [
{
"name": "balance",
"value": "345.22"
},
{
"name": "due_date",
"value": "16/11/2026"
}
]
},
{
"primaryReference": "CUST-1043",
"firstName": "Priya",
"phoneNumber": "07700900456",
"emailAddress": "priya@example.com",
"variables": [
{
"name": "balance",
"value": "120.00"
},
{
"name": "due_date",
"value": "18/11/2026"
}
]
}
]
}Scheduled email
{
"instanceName": "newsletter_email",
"batchName": "Winter newsletter",
"scheduledRunRangeStart": "2026-11-02T09:00:00",
"scheduledRunRangeEnd": "2026-11-02T17:00:00",
"records": [
{
"primaryReference": "CUST-1042",
"emailAddress": "sam@example.com",
"firstName": "Sam"
},
{
"primaryReference": "CUST-1043",
"emailAddress": "priya@example.com",
"firstName": "Priya"
}
]
}Voice call
{
"instanceName": "appointment_call",
"records": [
{
"primaryReference": "APT-5531",
"title": "Mr",
"surname": "Jones",
"phoneNumber": "07700900789",
"scheduledRun": "2026-11-03T10:00:00",
"variables": [
{
"name": "appointment_time",
"value": "11:30"
},
{
"name": "clinic",
"value": "Burnham"
}
]
}
]
}Request body
| Field | Type | Description |
|---|---|---|
instanceName required | string | The instance that will process these records. |
records required | array | One or more records. See Record. |
batchName | string | A name for this group of records, shown in reports. Only used when you submit more than one record. |
deletePendingRecords | boolean | Set true to delete any existing records that are still waiting to be processed. Default false. |
scheduledRunRangeStart | date-time | Start processing the records at this time, for example 2026-11-02T09:00:00. If omitted, each record’s own scheduledRun is used, or it is processed immediately. |
scheduledRunRangeEnd | date-time | Finish processing the records by this time. Use with scheduledRunRangeStart to spread a large send across a period. |
Record
| Field | Type | Description |
|---|---|---|
primaryReference required | string | Your unique reference for this contact, such as a customer or account number. |
secondaryReference | string | A second reference of your own. |
title, firstName, middleName, surname | string | The contact’s name, for personalising messages and calls. |
gender | string | UNKNOWN, MALE or FEMALE. |
dateOfBirth | date-time | The contact’s date of birth. |
emailAddress | string | Email address used by email actions. |
alternativeEmailAddress | string | A second email address. |
phoneNumber | string | The main number for SMS and voice calls, for example 07700900123 or 447700900123. |
alternativePhoneNumber | string | A second phone number. |
mailingAddressLine1 … mailingAddressLine4 | string | Postal address lines, for letter actions. The recipient’s name is added automatically, so line 1 is usually the house or street. |
mailingPostcode | string | Postcode. |
mailingCountryISO | string | 2-letter country code. |
scheduledRun | date-time | When to process this record. If omitted, it is processed immediately. |
variables | array | Extra values as {"name": "…", "value": "…"} pairs. Names must match the variables defined in the instance; values are always strings. |
Response
{
"apiName": "everymessage Workflow Web API",
"apiVersion": "1.1.2",
"apiTime": "11 October 2026 16:07:04",
"apiState": "RUNNING",
"responseStatusCode": 200,
"responseStatusReason": "None",
"processingTime": 31,
"body": {
"records": [
{
"primaryReference": "CUST-1042",
"workflowRecordId": 23997
},
{
"primaryReference": "CUST-1043",
"workflowRecordId": 23998
}
],
"recordsSubmitted": 2,
"recordsWithWarningFlag": 0
}
}
| Field | Type | Description |
|---|---|---|
responseStatusCode | integer | 200 on success. See the note above. |
responseStatusReason | string | None on success, otherwise the reason, for example UNAUTHORIZED. |
responseStatusMessage | string | A description of the problem, when there is one. |
processingTime | integer | Time taken on our side, in milliseconds. |
body.records | array | One entry per record: your primaryReference and our workflowRecordId. Keep the id to query the record later. |
body.recordsSubmitted | integer | Number of records accepted. |
body.recordsWithWarningFlag | integer | Number of records with a problem in their data, such as an invalid phone number or email address. They are still processed, because the instance may be able to use another channel, but actions using the invalid detail will fail. |
Query records
https://wfa.everymessage.com/rest/v1/records/queryReturns the progress of records you submitted, and optionally the SMS and email each one sent.
Request
{
"records": [
23997
],
"includeSmsInformation": true,
"includeEmailInformation": true
}Response
{
"apiName": "everymessage Workflow Web API",
"apiVersion": "1.1.2",
"apiTime": "11 October 2026 16:21:04",
"apiState": "RUNNING",
"responseStatusCode": 200,
"responseStatusReason": "None",
"processingTime": 47,
"body": {
"records": [
{
"dataReference": "23997",
"primaryReference": "CUST-1042",
"processingStatus": "COMPLETED",
"sentSmsMessages": [
{
"reference": "WF5-1-T-23997-1-0",
"originator": "EVERYMSG",
"to": "447700900123",
"sent": true,
"sentDate": "2026-10-11T10:52:18.52",
"delivered": true,
"deliveryCode": 0,
"deliveryDate": "2026-10-11T10:53:22.14"
}
],
"sentEmailDocuments": [
{
"reference": "23997-1",
"from": "accounts@example.com",
"to": [
"sam@example.com"
],
"sent": true,
"sentDate": "2026-10-11T10:52:22",
"deliveryFailure": false
}
]
}
]
}
}| Parameter | Type | Description |
|---|---|---|
records required | array | One or more workflowRecordId values from the submit response. |
includeSmsInformation | boolean | Include the SMS each record sent. |
includeEmailInformation | boolean | Include the email each record sent. |
Record status
| processingStatus | Meaning |
|---|---|
PENDING | Waiting to be processed, for example because it is scheduled. |
RUNNING | The journey is in progress. |
COMPLETED | The journey finished successfully. |
FAILED | The journey finished unsuccessfully. |
sentSmsMessages
| Field | Type | Description |
|---|---|---|
reference | string | Our reference for the SMS. |
originator, to | string | Who it was sent from and to. |
sent, sentDate | boolean, date-time | Whether and when it was sent. |
delivered, deliveryCode, deliveryDate | boolean, integer, date-time | The delivery result; deliveryCode 0 means no error. Left out until the network reports back. |
sentEmailDocuments
| Field | Type | Description |
|---|---|---|
reference | string | Our reference for the email. |
from, to | string, array | Sender address and recipient addresses. |
sent, sentDate | boolean, date-time | Whether and when it was sent. Left out until known. |
deliveryFailure, deliveryFailureDate, deliveryFailureMessage | boolean, date-time, string | Set if the email bounced; the message is usually the receiving mail server’s reason. |
Fields that are not known yet are left out, so treat them all as optional. dataReference is the workflowRecordId, and may be returned as a string.
Code examples
Both examples read the Workflow keys from EM_WORKFLOW_KEY and EM_WORKFLOW_SECRET. C# needs .NET 8 or later; Python needs 3.10 or later and pip install httpx.
Submit a record
C#
// Submit a record to an everymessage Workflow instance (.NET 8 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
var apiKey = Environment.GetEnvironmentVariable("EM_WORKFLOW_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_WORKFLOW_SECRET")!;
using var http = new HttpClient { BaseAddress = new Uri("https://wfa.everymessage.com/rest/v1/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));
var json = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// The instance decides what happens: email, SMS, a voice call, or a sequence of them.
var request = new SubmitRequest(
InstanceName: "payment_reminder",
Records:
[
new Record(
PrimaryReference: "CUST-1042",
FirstName: "Sam",
PhoneNumber: "07700900123",
EmailAddress: "sam@example.com",
Variables: [new("balance", "345.22"), new("due_date", "16/11/2026")])
]);
using var response = await http.PostAsJsonAsync("records/submit", request, json);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<Envelope<SubmitBody>>(json)
?? throw new InvalidOperationException("Empty response from everymessage");
// The API returns HTTP 200 even on errors: check responseStatusCode in the body.
if (result.ResponseStatusCode != 200)
throw new InvalidOperationException(
$"Submit failed: {result.ResponseStatusReason} {result.ResponseStatusMessage}");
// Keep workflowRecordId: it is how you query the record later.
foreach (var r in result.Body!.Records)
Console.WriteLine($"{r.PrimaryReference}: workflow record {r.WorkflowRecordId}");
Console.WriteLine($"{result.Body.RecordsSubmitted} submitted, {result.Body.RecordsWithWarningFlag} with warnings");
record SubmitRequest(string InstanceName, IReadOnlyList<Record> Records,
string? BatchName = null, bool DeletePendingRecords = false);
record Record(string PrimaryReference, string? FirstName = null, string? Surname = null,
string? PhoneNumber = null, string? EmailAddress = null, DateTime? ScheduledRun = null,
IReadOnlyList<Variable>? Variables = null);
record Variable(string Name, string Value);
record Envelope<T>(int ResponseStatusCode, string? ResponseStatusReason,
string? ResponseStatusMessage, T? Body);
record SubmitBody(IReadOnlyList<SubmittedRecord> Records, int RecordsSubmitted, int RecordsWithWarningFlag);
record SubmittedRecord(string PrimaryReference, long WorkflowRecordId);
Python
"""Submit a record to an everymessage Workflow instance (Python 3.10+, pip install httpx)."""
import os
import httpx
# The instance decides what happens: email, SMS, a voice call, or a sequence of them.
payload = {
"instanceName": "payment_reminder",
"records": [
{
"primaryReference": "CUST-1042",
"firstName": "Sam",
"phoneNumber": "07700900123",
"emailAddress": "sam@example.com",
"variables": [
{"name": "balance", "value": "345.22"},
{"name": "due_date", "value": "16/11/2026"},
],
}
],
}
with httpx.Client(
base_url="https://wfa.everymessage.com/rest/v1",
auth=(os.environ["EM_WORKFLOW_KEY"], os.environ["EM_WORKFLOW_SECRET"]),
timeout=30,
) as client:
response = client.post("/records/submit", json=payload)
response.raise_for_status()
result = response.json()
# The API returns HTTP 200 even on errors: check responseStatusCode in the body.
if result.get("responseStatusCode") != 200:
raise RuntimeError(f"Submit failed: {result.get('responseStatusReason')} "
f"{result.get('responseStatusMessage', '')}")
body = result["body"]
# Keep workflowRecordId: it is how you query the record later.
for r in body["records"]:
print(f"{r['primaryReference']}: workflow record {r['workflowRecordId']}")
print(f"{body['recordsSubmitted']} submitted, {body['recordsWithWarningFlag']} with warnings")
Query records
C#
// Check the progress of Workflow records, with the SMS and email they sent (.NET 8 or later)
// Usage: dotnet run -- <workflowRecordId> [<workflowRecordId> ...]
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
var apiKey = Environment.GetEnvironmentVariable("EM_WORKFLOW_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_WORKFLOW_SECRET")!;
using var http = new HttpClient { BaseAddress = new Uri("https://wfa.everymessage.com/rest/v1/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));
var json = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
NumberHandling = JsonNumberHandling.AllowReadingFromString // ids may arrive as "23996"
};
var query = new { records = args.Select(long.Parse).ToArray(), includeSmsInformation = true, includeEmailInformation = true };
using var response = await http.PostAsJsonAsync("records/query", query, json);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<Envelope<QueryBody>>(json)
?? throw new InvalidOperationException("Empty response from everymessage");
if (result.ResponseStatusCode != 200)
throw new InvalidOperationException(
$"Query failed: {result.ResponseStatusReason} {result.ResponseStatusMessage}");
foreach (var r in result.Body!.Records)
{
Console.WriteLine($"Record {r.DataReference} ({r.PrimaryReference}): {r.ProcessingStatus}");
foreach (var s in r.SentSmsMessages ?? [])
Console.WriteLine($" SMS {s.Reference}: sent {s.Sent}, delivered {s.Delivered?.ToString() ?? "awaiting receipt"}");
foreach (var e in r.SentEmailDocuments ?? [])
Console.WriteLine($" Email to {string.Join(", ", e.To ?? [])}: sent {e.Sent?.ToString() ?? "unknown"}" +
(e.DeliveryFailure == true ? $", failed: {e.DeliveryFailureMessage}" : ""));
}
record Envelope<T>(int ResponseStatusCode, string? ResponseStatusReason,
string? ResponseStatusMessage, T? Body);
record QueryBody(IReadOnlyList<WorkflowRecord> Records);
record WorkflowRecord(long DataReference, string? PrimaryReference, string ProcessingStatus,
IReadOnlyList<SentSms>? SentSmsMessages, IReadOnlyList<SentEmail>? SentEmailDocuments);
record SentSms(string? Reference, bool Sent, DateTime? SentDate, bool? Delivered,
int? DeliveryCode, DateTime? DeliveryDate);
record SentEmail(string? Reference, string? From, IReadOnlyList<string>? To, bool? Sent,
bool? DeliveryFailure, string? DeliveryFailureMessage);
Python
"""Check the progress of Workflow records (Python 3.10+, pip install httpx).
Usage: python query_workflow.py <workflowRecordId> [<workflowRecordId> ...]
"""
import os
import sys
import httpx
query = {
"records": [int(x) for x in sys.argv[1:]],
"includeSmsInformation": True,
"includeEmailInformation": True,
}
with httpx.Client(
base_url="https://wfa.everymessage.com/rest/v1",
auth=(os.environ["EM_WORKFLOW_KEY"], os.environ["EM_WORKFLOW_SECRET"]),
timeout=30,
) as client:
response = client.post("/records/query", json=query)
response.raise_for_status()
result = response.json()
if result.get("responseStatusCode") != 200:
raise RuntimeError(f"Query failed: {result.get('responseStatusReason')} "
f"{result.get('responseStatusMessage', '')}")
for r in result["body"]["records"]:
print(f"Record {r['dataReference']} ({r.get('primaryReference')}): {r['processingStatus']}")
for s in r.get("sentSmsMessages") or []:
print(f" SMS {s.get('reference')}: sent {s.get('sent')}, "
f"delivered {s.get('delivered', 'awaiting receipt')}")
for e in r.get("sentEmailDocuments") or []:
failed = f", failed: {e.get('deliveryFailureMessage')}" if e.get("deliveryFailure") else ""
print(f" Email to {', '.join(e.get('to') or [])}: sent {e.get('sent', 'unknown')}{failed}")
