Transactional email sending.
| POST | /api/v1/emails | Invia un'email transazionale. `idempotencyKey` nel body obbligatoria (min 8 char); `from`/`replyTo` accettano 'Nome <[email protected]>'.(scope: email:send) |
| GET | /api/v1/emails/:id | Stato di un invio (metadati + timeline eventi). |
| POST | /api/v1/messages | Invio transazionale unificato email/WhatsApp/Telegram.(scope: messages:send) |
POST /api/v1/emails
Body parameters (JSON):
| Field | Type | Required | Notes |
|---|---|---|---|
to | string or array | yes | One or more recipients: [email protected], Name <[email protected]>, several addresses comma separated in the same string, or an array. |
from | string | yes | [email protected] or Name <[email protected]>. The domain must be verified. |
subject | string | yes* | Min 1 char. Optional if the template supplies the subject. |
html | string | yes* | HTML body. Use templateId instead if you prefer. |
text | string | no | Plain text version (multipart part). |
replyTo | string | no | Reply-To: [email protected] or Name <[email protected]>. |
cc | string or array | no | Visible copies: they appear in the Cc header. Same forms as to. |
bcc | string or array | no | Blind copies: they appear in no header of the message. Same forms as to. |
templateId | string | no | Id (uuid) or name of a tenant template; alternative to html. |
variables | object | no | Interpolation variables for the template. |
attachments | array | no | Attachments: { id }, { filename, content } (base64) or { filename, url }. See Attachments. |
idempotencyKey | string | yes | Min 8 characters. Prevents double sends on retries. Goes in the body, not in a header. |
disableTracking | boolean | no | Turns off pixel and click redirects for this send (default false). |
disableUtm | boolean | no | Skips 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 field | Notes |
|---|---|
index | Position in the emails array you sent. |
status | accepted or failed. |
id | Message id, present on accepted items (duplicates included). |
duplicate | true if that idempotencyKey had already been accepted: this is not an error. |
droppedRecipients | Copies 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).