Send a transactional message
Send an OTP or confirmation to one customer through a transactional endpoint.
Use a transactional endpoint for one-to-one messages the customer is waiting for, such as OTPs, order confirmations and password resets. These sends skip frequency caps and do-not-disturb, and they go out at high priority.
POST https://api.true-engage.com/v1/transactional/{slug}/send
Before you start
- In the console, go to Engage, then Transactional, and create an endpoint. It is tied to a template and has a slug.
- Note the slug shown there. It goes in the URL.
- Create a server key with the
campaigns.create_managescope. See API keys.
The endpoint's settings decide whether it also bypasses opt-out. By default it does not, so consent is still respected.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
idempotencyKey |
string | Yes | Up to 200 characters. A repeat within 24 hours returns the first result and sends nothing new. |
userId |
string | One of userId or profileId |
Your external id for the customer. Preferred. |
profileId |
string (UUID) | One of userId or profileId |
The TrueEngage profile id, when you have it. |
to |
string | Conditional | An email address or E.164 phone number. Use it when the profile has no address, or with an unknown userId to create the profile. Up to 320 characters. |
variables |
object | Conditional | Values for the template, such as { "otp": "483920" }. Every variable the endpoint marks as required must be present. |
deviceUid |
string | No | The originating device. Honoured only if the endpoint accepts it and the device belongs to this user. |
platform |
string | No | android, ios or web. |
Example
curl -X POST https://api.true-engage.com/v1/transactional/login-otp/send \
-u "test_xxxxxxxx:YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{
"userId": "C10093",
"variables": { "otp": "483920", "expiryMinutes": 10 },
"idempotencyKey": "otp-C10093-1760779800"
}'
const auth = Buffer.from(`${process.env.TE_APP_KEY}:${process.env.TE_SECRET}`).toString('base64');
const res = await fetch('https://api.true-engage.com/v1/transactional/login-otp/send', {
method: 'POST',
headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: 'C10093',
variables: { otp: '483920', expiryMinutes: 10 },
idempotencyKey: `otp-C10093-${Date.now()}`,
}),
});
Response
Status 202 Accepted. The message is queued for delivery.
{
"requestId": "0b1c6f2e-6a1d-4c58-9f47-2b7d6d1f8a10",
"data": { "dispatchJobId": "7c4f1d52-9b0e-4f3a-8f7e-1a2b3c4d5e6f", "deduplicated": false }
}
deduplicated: true means this idempotencyKey was already used and nothing new was sent.
Accepted does not mean delivered. Delivery results appear on the user's profile timeline and in Analytics.
Errors
| Status | Code | Cause |
|---|---|---|
| 404 | TRANSACTIONAL_ENDPOINT_NOT_FOUND |
No endpoint with this slug in the key's environment. |
| 422 | TRANSACTIONAL_VARIABLES_MISSING |
A required variable is absent. data.missing lists them. |
| 404 | USER_NOT_FOUND |
No profile matches, and no to was given to create one. |
| 429 | RATE_LIMITED |
Over 6,000/min per key, or over the endpoint's own per-minute limit. Honour Retry-After. |
| 401 or 403 | INVALID_API_KEY, INSUFFICIENT_SCOPE, KEY_KIND_NOT_ALLOWED |
Credential problem. Only server keys may call this endpoint. |
See Errors.
Tips
- Build the
idempotencyKeyfrom something stable in your system, such as the OTP request id, so a retry never sends a second SMS. - Use Test keys while you build. In Test, only people on the allowlist receive messages. See Test and Live.
Was this page helpful?