Email

Transactional email sending.

POST/api/v1/emailsInvia un'email transazionale. `idempotencyKey` nel body obbligatoria (min 8 char); `from`/`replyTo` accettano 'Nome <[email protected]>'.(scope: email:send)
GET/api/v1/emails/:idStato di un invio (metadati + timeline eventi).
POST/api/v1/messagesInvio transazionale unificato email/WhatsApp/Telegram.(scope: messages:send)

POST /api/v1/emails

Body parameters (JSON):

FieldTypeRequiredNotes
tostring or arrayyesOne or more recipients: [email protected], Name <[email protected]>, several addresses comma separated in the same string, or an array.
fromstringyes[email protected] or Name <[email protected]>. The domain must be verified.
subjectstringyes*Min 1 char. Optional if the template supplies the subject.
htmlstringyes*HTML body. Use templateId instead if you prefer.
textstringnoPlain text version (multipart part).
replyTostringnoReply-To: [email protected] or Name <[email protected]>.
ccstring or arraynoVisible copies: they appear in the Cc header. Same forms as to.
bccstring or arraynoBlind copies: they appear in no header of the message. Same forms as to.
templateIdstringnoId (uuid) or name of a tenant template; alternative to html.
variablesobjectnoInterpolation variables for the template.
attachmentsarraynoAttachments: { id }, { filename, content } (base64) or { filename, url }. See Attachments.
idempotencyKeystringyesMin 8 characters. Prevents double sends on retries. Goes in the body, not in a header.
disableTrackingbooleannoTurns off pixel and click redirects for this send (default false).
disableUtmbooleannoSkips UTM auto-tagging of links (default false).
* subject and html are required unless a templateId supplies them.

Max 50 recipients per message (to plus cc plus bcc), an SES limit: past that the response is 422 too_many_recipients. Every recipient consumes one unit of the plan's monthly quota. Copies in the suppression list (bounce, complaint, unsubscribe) are removed from the send and reported back in droppedRecipients; the request is a 409 only when no to recipient is left to send to. Opens and clicks are per message, not per recipient: they cannot tell which of the copies opened.

Every address field accepts both [email protected] and Name <[email protected]>, and to, cc and bcc also take several addresses at once, as an array or comma separated inside one string ("[email protected], [email protected]"). A comma inside a quoted display name does not split the list: "Neri, Sara" <[email protected]> is one recipient. Multiple to addresses land in the To header, not in Cc, and GET /api/v1/emails/{id} returns them in toAddresses.

Responses: 202 ({ id, status: "queued" }), 200 ({ status: "duplicate", idempotencyKey, id }), 422 (validation), 402 (plan quota), 403 (domain_not_verified), 409 (suppressed).

Example

import { Sending } from "sending-sdk";
 
const sending = new Sending({ apiKey: process.env.SENDING_API_KEY! });
 
const { id } = await sending.emails.send({
  from: "Acme <[email protected]>",
  to: "[email protected]",
  subject: "Hello",
  html: "<p>Hey</p>",
  replyTo: "Support <[email protected]>",
  idempotencyKey: "hello-sara-001",
});

POST /api/v1/emails/batch

Up to 100 emails in one request. The body is { "emails": [...] } and each item is exactly the body of POST /api/v1/emails, idempotencyKey included.

The outcome is partial, not all-or-nothing: the response is 207 and results is aligned by index with the array you sent, so one invalid address out of 60 does not stop the other 59.

Result fieldNotes
indexPosition in the emails array you sent.
statusaccepted or failed.
idMessage id, present on accepted items (duplicates included).
duplicatetrue if that idempotencyKey had already been accepted: this is not an error.
droppedRecipientsCopies removed because they are suppressed.
error{ code, message } on failures: validation_error, domain_not_verified, suppressed, template_not_found, quota_exceeded, too_many_recipients.

Idempotency applies per item: a retried batch produces no double sends, and items already accepted come back accepted with duplicate: true. For rate limiting purposes the call counts as one request.

const outcome = await sending.emails.sendBatch([
  { from: "Acme <[email protected]>", to: "[email protected]", subject: "Hello", html: "<p>hey</p>", idempotencyKey: "b-001" },
  { from: "Acme <[email protected]>", to: "[email protected]", subject: "Hello", html: "<p>hey</p>", idempotencyKey: "b-002" },
]);
for (const r of outcome.results) {
  if (r.status === "failed") console.error(`item ${r.index}: ${r.error?.code}`);
}

Response:

{
  "accepted": 2,
  "failed": 1,
  "results": [
    { "index": 0, "status": "accepted", "id": "8f1c…" },
    { "index": 1, "status": "accepted", "id": "0b22…", "duplicate": true },
    { "index": 2, "status": "failed", "error": { "code": "suppressed", "message": "complaint" } }
  ]
}

GET /api/v1/emails/:id

State of one send: use the id returned when you sent it.

const status = await sending.emails.get(id);
console.log(status.status, status.events);

It returns status, metadata (to, from, subject, provider, createdAt), the attachments of the send (id, filename, contentType, size) and the events timeline (sent, delivered, open, click, bounce, complaint).

Full schema and a "try it" console in the interactive API reference (OpenAPI).