POST

/v1/send

Unified endpoint to send a message on any channel.

Endpoint

POST https://api.yoursend.dev/v1/send

Requires an Authorization: Bearer ys_live_... header. Use a ys_test_... key to run in sandbox mode — requests are validated and logged but nothing is delivered or billed.

Request Body

FieldTypeRequiredDescription
channelstringYes"email" | "sms" | "voice" | "whatsapp" | "auto"
tostringYesRecipient (email or E.164 phone)
subjectstringCond.Email subject (or comes from the template)
htmlstringCond.HTML body (email only)
textstringNoPlain text body (email fallback)
bodystringCond.Message body (SMS/voice/WhatsApp)
template_idstringCond.Template ID (alternative to inline content)
dataobjectNoMerge-tag values for {{...}} placeholders
fallbackstringNoChannel to try if the primary one fails
fallback_tostringNoAddress for the fallback channel
media_urlsarrayNoUp to 10 https media URLs (MMS/RCS/WhatsApp)
ccarrayNoCopied recipients, email only (max 24) — each is billed as a message
bccarrayNoBlind-copied recipients, email only (max 24) — each is billed as a message
attachmentsarrayNoFiles to attach, email only — see Attachments below

Omit subject, html, text, body, and template_id to send an OTP — YourSend generates and delivers the code. The sender address comes from your verified domain; there is no from field.

Copy Recipients

cc and bcc take arrays of email addresses. Both are email only — sending them on any other channel returns channel_mismatch rather than silently dropping them. Each list accepts at most 24 addresses, and addresses are trimmed before validation, so a pasted "a@example.com, b@example.com" split into an array works as-is. One invalid address rejects the whole request with invalid_request.

Billing: every recipient is a delivered message, so a send is billed as 1 + cc + bcc. One email to a to plus three cc addresses costs four messages, and the balance check reserves all four up front.

curl -X POST https://api.yoursend.dev/v1/send \
  -H "Authorization: Bearer ys_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "email",
    "to": "customer@example.com",
    "cc": ["manager@example.com"],
    "bcc": ["archive@example.com"],
    "subject": "Your invoice",
    "html": "<p>Invoice attached.</p>"
  }'

Billed as 3 messages. bcc recipients are hidden from everyone on the message, including each other.

Attachments

attachments is an array of { filename, content, content_type }, where content is the file encoded as base64 and content_type is optional. Up to 10 files, 10MB in total. Attachments are email only — on another channel they return channel_mismatch (use media_urls for MMS, WhatsApp and RCS). Executable types such as .exe, .bat and .js are rejected with invalid_request. A message with attachments needs a body (html, text, body or template_id).

cURL
curl -X POST https://api.yoursend.dev/v1/send \
  -H "Authorization: Bearer ys_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "jane@example.com",
    "channel": "email",
    "subject": "Your invoice",
    "html": "<p>Invoice attached.</p>",
    "attachments": [
      { "filename": "invoice.pdf", "content": "JVBERi0xLjQK...", "content_type": "application/pdf" }
    ]
  }'
SDK
import { readFileSync } from 'node:fs';

await ys.send({
  to: 'jane@example.com',
  channel: 'email',
  subject: 'Your invoice',
  html: '<p>Invoice attached.</p>',
  attachments: [{
    filename: 'invoice.pdf',
    content: readFileSync('invoice.pdf').toString('base64'),
    content_type: 'application/pdf',
  }],
});

Encode the file's raw bytes.PDF, PNG, JPEG, GIF and ZIP/Office files are checked before sending. If the content isn't a valid file of that type (most often because it was read as text, e.g. readFileSync(path, 'utf8'), FileReader.readAsText or await res.text()), the request fails with invalid_request and a message explaining the fix, instead of delivering a blank file. Use readFileSync(path).toString('base64'), Buffer.from(await res.arrayBuffer()).toString('base64') for a fetched file, or FileReader.readAsDataURL in a browser (strip the data:...;base64, prefix). If you generate PDFs, wait for the document to finish before encoding it.

Response

200 OK
{
  "message_id": "8f14e45f-ceea-467a-9b3a-1d2c3e4f5a6b",
  "status": "sent",
  "channel": "email",
  "to": "user@example.com"
}
400 Bad Request
{
  "error": {
    "code": "invalid_request",
    "message": "Missing required field: to",
    "status": 400
  }
}
403 Forbidden
{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid API key.",
    "status": 403
  }
}

Examples

Send email

curl -X POST https://api.yoursend.dev/v1/send \
  -H "Authorization: Bearer ys_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "email",
    "to": "user@example.com",
    "subject": "Welcome!",
    "html": "<h1>Hello</h1>"
  }'

Send SMS OTP

No body or template means YourSend generates and sends the code.

curl -X POST https://api.yoursend.dev/v1/send \
  -H "Authorization: Bearer ys_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "to": "+14155551234"
  }'