GenieOSdocs

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.

On this page