Transactional sending
Send a single message, batch up to 1,000 at a time, schedule sends in the future, and attach files.
A transactional send is a single message addressed to a single human in direct response to something they did — a receipt, password reset, login code, shipping notification. It is not bulk; it is not lifecycle drip; it is the API surface most teams reach for first.
Single send
Canonical route (live today): POST /v1/templates/{key}/send.
Thin /v1/transactional/* aliases ship in Phase 1C.
import { GenieOS } from 'genieos';
const gos = new GenieOS();
const res = await gos.templates.send(
{
to: 'ada@example.com',
template: 'order_confirm',
variables: {
first_name: 'Ada',
order_total: '\u00a342.99',
order_lines: ['Hardback notebook', 'Felt-tip pen'],
},
from: { email: 'orders@yourbrand.com', name: 'Your Brand' },
// Join keys — echoed on webhooks + GET /v1/sends/{id}. Not indexed.
metadata: { order_id: 'ord_8a72c0' },
},
{ idempotencyKey: 'ord_8a72c0:order_confirm' },
);
console.log(res.id); // snd_…from genieos import GenieOS
gos = GenieOS()
res = gos.templates.send(
to="ada@example.com",
template="order_confirm",
variables={
"first_name": "Ada",
"order_total": "\u00a342.99",
"order_lines": ["Hardback notebook", "Felt-tip pen"],
},
from_={"email": "orders@yourbrand.com", "name": "Your Brand"},
metadata={"order_id": "ord_8a72c0"},
idempotency_key="ord_8a72c0:order_confirm",
)
print(res.id)curl https://api.genieos.pro/v1/templates/order_confirm/send \
-H "Authorization: Bearer $GENIEOS_API_KEY" \
-H "Idempotency-Key: ord_8a72c0:order_confirm" \
-H "Content-Type: application/json" \
-d '{
"to": "ada@example.com",
"variables": { "first_name": "Ada" },
"from": { "email": "orders@yourbrand.com", "name": "Your Brand" },
"metadata": { "order_id": "ord_8a72c0" }
}'The response is a SendPublic object (id, status, connectorProvider,
events[], optional metadata / simulation). Read it again with
GET /v1/sends/{id} or list with GET /v1/sends.
Status moves sent → delivered (and opens/clicks/bounces) via provider
webhooks. Real-time updates use send.* events — see Webhooks.
send.queued means GenieOS accepted the send; it is not mailbox delivery.
Sandbox (gos_test_*) keys never contact a mailbox. They emit
send.queued then a marked synthetic terminal event (default
send.delivered). Optional body field: simulationScenario.
Batch
Batch send (POST /v1/transactional/batch, cap 50) is Phase 1C — not
live yet. Until then, loop single sends with distinct Idempotency-Key
values per recipient.
Scheduling
Deferred send_at / cancel-scheduled is not live yet (Phase 1C+).
Send immediately via POST /v1/templates/{key}/send.
Attachments
Transactional supports inline attachments up to 10 MB total per message.
await gos.templates.send({
to: 'ada@example.com',
template: 'invoice',
variables: { amount: '\u00a342.99' },
attachments: [
{
filename: 'invoice-INV-1024.pdf',
content_type: 'application/pdf',
data_base64: pdfBuffer.toString('base64'),
},
],
});Inline images for the body are referenced by cid:filename:
attachments: [
{
filename: 'header.png',
content_type: 'image/png',
data_base64: pngBuffer.toString('base64'),
inline: true, // sets Content-Disposition: inline; cid set to "header.png"
},
],
// In the template: <img src="cid:header.png" alt="" />Reply-to and headers
Override per send when you need to thread a customer reply into a specific mailbox or add an Auto-Submitted header.
await gos.templates.send({
to: 'ada@example.com',
template: 'support_response',
variables: { ticket_id: 'tkt_123' },
reply_to: 'support+tkt_123@yourbrand.com',
headers: {
'Auto-Submitted': 'auto-replied',
'In-Reply-To': '<msg-orig@yourbrand.com>',
},
});We strip blacklisted headers (Bcc, Resent-*, Return-Path, anything
that would compromise deliverability or impersonate). The full deny-list
is in the audit of the transactional.send call.
Suppression lists
Recipients who bounce hard, mark as spam, or hit the unsubscribe link land
in the workspace\u2019s suppression list automatically. Sending to a
suppressed recipient succeeds the API call but emits
transactional.suppressed and skips delivery. You can list and clear
suppressions via GET /v1/suppressions and DELETE /v1/suppressions/{email}.
Don\u2019t bypass suppression in production
The flag exists for support flows and re-onboarding tools. Sending to a suppressed recipient without that user\u2019s explicit re-consent puts your domain reputation at risk and is a violation of CAN-SPAM / CASL / GDPR. Just don\u2019t.
Rendering preview
Want to see the rendered HTML / text without actually sending?
curl https://api.genieos.pro/v1/transactional/preview \
-H "Authorization: Bearer $GENIEOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template": "welcome", "variables": { "first_name": "Ada" } }'Transactional SMS
Transactional SMS uses the Messaging reserved-key kit. Browse the platform keys, preview the exact text, then send through the same Messaging controller that powers the Inbox.
curl https://api.genieos.pro/v1/messaging/transactional/catalog \
-H "Authorization: Bearer $GENIEOS_API_KEY"curl -X POST https://api.genieos.pro/v1/messaging/transactional \
-H "Authorization: Bearer $GENIEOS_API_KEY" \
-H "Idempotency-Key: auth_login_482915" \
-H "Content-Type: application/json" \
-d '{
"templateKey": "signin-code",
"to": "+447700900123",
"variables": { "code": "482915" }
}'For raw E.164 sends outside authentication-class keys, include
consentProofId. Magic-link SMS must use a branded short-link domain;
generic shorteners are rejected before Twilio is called.
Returns { html, text, subject }. Same code path as send; same schema
enforcement; no inbox movement; no charge.