# POSTA — Complete developer reference > POSTA is email infrastructure for developers. Use its REST API or official Node/TypeScript SDK to send transactional and broadcast email, inspect delivery state, validate recipients, receive signed webhooks, and manage inbound conversations. Production base URL: `https://posta.esaart.studio` Public product pages: [pricing](https://posta.esaart.studio/pricing), [system status](https://posta.esaart.studio/status), [security](https://posta.esaart.studio/security), and [changelog](https://posta.esaart.studio/changelog). This document describes the implemented public interface. Fields shown for the REST API use snake_case. The Node/TypeScript SDK exposes the corresponding fields in camelCase. ## Core rules - Create an API key in Dashboard → API Keys. Keys begin with `pa_`. - Keep API keys server-side. Do not expose them in browser JavaScript, mobile applications, public repositories, or logs. - Authenticate with `Authorization: Bearer pa_...`. - Send only from a verified domain connected to the same POSTA project. - A sending-only key can send email but cannot list message history. A full-permission key can do both. - A key may be restricted to one sending domain. - POSTA applies project quotas, per-minute and daily limits, suppression checks, and reputation controls before dispatch. ## Quickstart with cURL ```bash curl -X POST https://posta.esaart.studio/api/v1/emails \ -H "Authorization: Bearer $POSTA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: welcome-user-482" \ -d '{ "from": "Product ", "to": "maya@example.net", "subject": "Your workspace is ready", "html": "Welcome to Acme.", "text": "Welcome to Acme." }' ``` A successful immediate send returns HTTP `201` and an object containing `id`, `object`, `from`, `to`, `subject`, `status`, `sandbox`, and `created_at`. A repeated `Idempotency-Key` returns the stored response with `Idempotent-Replay: true` and does not send twice. ## Node and TypeScript SDK Install the zero-runtime-dependency official package: ```bash npm install @esaart/posta ``` ```ts import { Posta } from "@esaart/posta"; const posta = new Posta(process.env.POSTA_API_KEY!); const result = await posta.emails.send({ from: "Product ", to: "maya@example.net", subject: "Your workspace is ready", html: "Welcome to Acme.", idempotencyKey: "welcome-user-482", }); ``` `new Posta(apiKey, options?)` accepts an optional `baseUrl`, custom `fetch`, `timeoutMs`, and retry count. The default base URL is `https://posta.esaart.studio`, timeout is 30 seconds, and the SDK retries retryable `429` and `5xx` responses twice by default. ## Send one email Endpoint: `POST /api/v1/emails` Required request fields: - `from`: sender address, optionally formatted as `Name `. - `to`: one recipient string or an array of recipient strings. - `subject`: required unless the selected stored template supplies one. - At least one of `html`, `text`, or `template`. Optional request fields: - `reply_to`: reply destination. - `template`: stored template ID or slug. - `variables`: string-to-string values interpolated into `{{tokens}}`. - `headers`: custom string headers. - `attachments`: either small base64 attachments (`filename`, `content`, optional `content_type`) or private Blob references returned by `POST /api/v1/attachments` (`filename`, `blob_name`, `size_bytes`, optional `content_type`). - `send_at`: future ISO 8601 timestamp for scheduled delivery. Use the HTTP `Idempotency-Key` header to make retries safe. In the Node SDK use `idempotencyKey`. In the Node SDK, REST fields `reply_to`, `content_type`, and `send_at` are exposed as `replyTo`, `contentType`, and `scheduledAt`. Limits for a single request: - Maximum 50 recipients. - Maximum 500 subject characters. - Maximum approximately 1 MB combined HTML and text. - Maximum 10 direct-upload attachments, 25 MB per file and 100 MB total. Selections up to 7 MB raw are delivered as MIME; larger selections become private seven-day download links so Azure ACS does not reject the message. - Legacy inline base64 remains limited to approximately 2.5 MB decoded total because of the HTTP function-body boundary. - Scheduled emails support direct-upload attachment references. ## Upload an attachment Preferred SDK flow: ```ts const attachment = await posta.attachments.upload({ filename: "report.pdf", data: reportBytes, contentType: "application/pdf", }); await posta.emails.send({ from: "hello@mail.example.com", to: "maya@example.net", subject: "Report", text: "Attached.", attachments: [attachment], }); ``` At REST level, call `POST /api/v1/attachments`, PUT the raw bytes to the returned `upload_url`, then pass `blob_name`, `size_bytes`, `filename`, and `content_type` to the send endpoint. Upload URLs expire after ten minutes and never expose the storage account credential. Recipients already present in the project's suppression list are skipped. If all recipients are suppressed, the request fails with HTTP `422`. ## Templates Send a stored template by ID or slug and supply string variables: ```json { "from": "Product ", "to": "maya@example.net", "template": "workspace-ready", "variables": { "name": "Maya", "workspace": "Northstar" } } ``` The template supplies HTML and may supply the subject. A subject included in the request overrides the stored template subject. Variables also interpolate into inline subject, HTML, and text content. ## Batch sending Endpoint: `POST /api/v1/emails/batch` The body may be a bare array or `{ "emails": [...] }`. Each item supports the same content fields as an immediate single send, except scheduled sending is not supported by the batch endpoint. Limits: - Maximum 100 email items. - Maximum 50 recipients per item. - Maximum 100 recipients across the complete batch. - Each batch item supports the same direct-upload references as a single send. Legacy inline base64 remains limited to approximately 3 MB decoded per item. The response is `{ "object": "list", "data": [...] }`. Each result contains an email ID and status or an item-level error, allowing partial success. ## List emails Endpoint: `GET /api/v1/emails?limit=50` Requires a full-permission API key. `limit` defaults to 50 and is capped at 100. The Node SDK equivalent is `posta.emails.list({ limit })`. ## Validate recipients Endpoint: `POST /api/v1/validate` Validate one address: ```json { "email": "maya@example.net" } ``` Or up to 100 addresses: ```json { "emails": ["maya@example.net", "support@example.net"] } ``` The result reports `valid`, risk level, reason, and checks for syntax, disposable domains, role addresses, likely typos, and MX availability. Validation is a hygiene signal, not a guarantee that a mailbox exists. ## Webhooks Create webhook endpoints in the dashboard and choose subscribed event types. Implemented outbound event types include: - `email.sent` - `email.delivered` - `email.opened` - `email.clicked` - `email.bounced` - `email.complained` - `email.received` POSTA signs webhook bodies with HMAC-SHA256 in the `Posta-Signature` header and records delivery attempts. Non-successful deliveries are retried. Verify the raw request body before parsing JSON: ```ts import { Posta } from "@esaart/posta"; const valid = Posta.webhooks.verify(rawBody, signature, webhookSecret); if (!valid) throw new Error("Invalid POSTA webhook signature"); ``` Use the webhook endpoint's own signing secret, not an API key. Deduplicate processing using the event identifier from the payload and make handlers idempotent. ## Inbound email POSTA can normalize incoming messages into hosted conversations and emit `email.received`. An inbound provider must first be connected to POSTA and its MX records configured. Inbound provider endpoints are deployment plumbing and should not be called by application clients directly. Application integrations should consume normalized `email.received` webhooks or work with conversations in the POSTA dashboard. Inbound content can include text, HTML, headers, and attachment metadata. ## Domains and deliverability Sending domains are managed in the dashboard. Publish every DNS record shown by POSTA, including ownership verification, SPF, DKIM selectors, and DMARC guidance, then use Check verification. Do not send from an unverified or unrelated domain. POSTA records delivery, bounce, complaint, open, and click state from Azure Communication Services events. Repeated hard bounces and complaints are added to suppressions and can pause a project's sending to protect reputation. ## Errors and retry behavior Errors use an object shaped like: ```json { "error": { "message": "Human-readable explanation." } } ``` Common statuses: - `400`: malformed JSON. - `401`: missing or invalid API key. - `402`: monthly plan quota reached. - `403`: permission, domain-scope, or project-pause failure. - `422`: invalid fields, limits, template, recipient, or suppression failure. - `429`: rate limit reached; respect `Retry-After` when present. - `502`: upstream email provider send failed. - `503`: required sending or quota infrastructure is unavailable. Retry `429` and transient `5xx` responses with exponential backoff and jitter. Always supply an `Idempotency-Key` when retrying a send. Do not retry validation or permission errors without changing the request or credentials. ## Product surfaces - Dashboard: projects, messages, domains, API keys, templates, layouts, webhooks, inbound inbox, audiences, suppressions, deliverability, usage, billing, members, audit, and settings. - Public API: send, batch send, list, and validate. - Node/TypeScript SDK: send, templates, batch, list, validation, webhook verification. - CLI: authenticate a developer machine and work with POSTA from a terminal. - Mobile app: operational access to projects, sending, messages, domains, templates, webhooks, inbound conversations, billing, and notifications. ## Canonical pages - Documentation: https://posta.esaart.studio/docs - Protocol overview: https://posta.esaart.studio/protocol - Delivery network: https://posta.esaart.studio/network - Privacy: https://posta.esaart.studio/privacy - Terms: https://posta.esaart.studio/terms - Abuse policy: https://posta.esaart.studio/abuse - Health: https://posta.esaart.studio/api/health