Send an email
Queues an email from the API key's environment and returns it with status: "queued"; it is handed to the delivery provider moments later. Follow it with GET /v1/emails/{id} or the email.sent / email.failed webhooks. Live (coss_live_…) keys must send from a verified domain of the organization. Sandbox (coss_test_…) keys accept any from and never deliver anything; the response has sandbox: true. Recipients on the environment's suppression list (for every email, or for the email's category) are skipped and get the status suppressed. With a future scheduled_at the email is stored with the status scheduled and sent at that time; the domain and the suppression list are checked again then. Attachments are base64-encoded; only metadata is kept after sending. Recipients count against the organization's send quota (per second and per day; sandbox environments have their own daily quota). With open or click tracking on (the environment's settings, or tracking), the HTML body is delivered with a tracking pixel and/or links rewritten to COSS redirects; the stored html stays as sent.
bearerAuthAuthorizationBearer <token>A secret API key from the dashboard: coss_live_… for production environments, coss_test_… for sandbox environments.
Idempotency-Key?stringMakes retries safe: a repeated request with the same key and body returns the original email (with its current status) instead of sending again, for 24 hours. 1-256 characters.
1 <= length <= 256Coss-Version?"2026-10-10"The API version (a date) to use for this request. Defaults to your organization's version, pinned to the latest one when it first used the API and upgraded from the dashboard. The SDK always sends the version it was built for.
"2026-10-10"application/json- body
from*stringAn email address, optionally with a display name: "Acme hello@acme.com".
to*|array<>cc?|array<>bcc?|array<>reply_to?|array<>subject*string1 <= length <= 998html?stringHTML body. Provide html, text, or both.
text?stringPlain-text body.
headers?Up to 15 custom headers. Names are case-insensitive.
tags?array<>items <= 50category?stringWhat kind of email this is, e.g. "marketing". Recipients suppressed for this category (or for every email) are skipped.
^[A-Za-z0-9_-]{1,256}$scheduled_at?stringSend the email at this time instead of now (ISO 8601 with an offset, at most 30 days ahead). A time that is not in the future sends immediately. Scheduled emails can be rescheduled or canceled until they are sent.
date-timeattachments?array<>Up to 20 files, at most 25 MB combined.
items <= 20tracking?Overrides the environment's tracking settings (its default, or the rule for this email's category) for this email. Omitted fields keep the configured setting.
The email, accepted for delivery (queued or scheduled). A replay returns the original email with its current status.
application/json- response
object*"email""email"id*stringfrom*stringto*array<string>cc*array<string>bcc*array<string>reply_to*array<string>subject*stringhtml*string|nulltext*string|nullheaders*tags*array<>category*string|nullstatus*stringWhether the email was handed to the delivery provider. scheduled emails wait for scheduled_at; canceled ones were canceled before that. Per-recipient delivery is in recipients.
"queued""sent""failed""scheduled""canceled"recipients*array<>attachments*array<>Attachment metadata. Content is never returned, and is discarded once sent.
sandbox*booleanTrue when sent from a sandbox environment (never delivered).
tracking*Open and click tracking, as resolved when the email was sent, with engagement so far. Opens can be inflated by mail clients that prefetch images, clicks by link scanners.
created_at*stringdate-timesent_at*|date-timescheduled_at*|When the email was scheduled to be sent; null if it was not.
date-timebatch_id*|The batch that sent the email; null if it was sent on its own.
import { Coss } from "coss";const coss = new Coss({ apiKey: process.env.COSS_API_KEY, baseUrl: "https://api.coss.com",});const { data, error } = await coss.email.send({ from: "Acme <onboarding@acme.com>", to: "user@example.com", subject: "Welcome to Acme", html: "<p>Thanks for signing up!</p>", tags: [ { name: "template", value: "welcome", }, ], category: "onboarding",});{ "object": "email", "id": "email_01k7c8m4q2f3e9v6t5r1n0p8yz", "from": "Acme <onboarding@acme.com>", "to": [ "user@example.com" ], "cc": [], "bcc": [], "reply_to": [], "subject": "Welcome to Acme", "html": "<p>Thanks for signing up!</p>", "text": null, "headers": {}, "tags": [ { "name": "template", "value": "welcome" } ], "category": "onboarding", "status": "sent", "recipients": [ { "address": "user@example.com", "type": "to", "status": "delivered", "last_event_at": "2026-10-10T12:00:02.000Z" } ], "sandbox": false, "tracking": { "opens": false, "clicks": false, "open_count": 0, "first_opened_at": null, "last_opened_at": null, "click_count": 0, "first_clicked_at": null, "last_clicked_at": null }, "attachments": [], "created_at": "2026-10-10T12:00:00.000Z", "sent_at": "2026-10-10T12:00:01.000Z", "scheduled_at": null, "batch_id": null}Send a batch of emails POST
Queues up to 100 emails in one request. Each email is handled exactly as by `POST /v1/emails` (sender checks, suppressions, scheduling, attachments, tracking), but the batch is all or nothing: if any email is invalid, or any `from` is not a verified domain (live keys), nothing is stored and the error names the email (e.g. `emails.3.to`). One `Idempotency-Key` covers the whole batch. The emails' recipients are charged against the send quota together, so a batch with more recipients than the quota allows at once is refused (`quota_exceeded` without `Retry-After`): split it. The request body shares the 40 MB limit of a single send.
Create a log drain POST
Streams the environment's logs to a URL: every line `GET /v1/logs` returns (API requests, email events, webhook attempts) and the environment's audit events, posted as signed NDJSON batches of up to 500 lines or 1 MB, usually within seconds. Log drains belong to the API key's own environment: a `coss_test_…` key only ever sees sandbox drains, and a `coss_live_…` key only production ones. The response carries the signing secret, which is returned only here: store it, and verify every batch with it (`verifyLogDrainBatch` in the SDK).