# Task: send transactional email through our own email API Add outgoing transactional email (sign-up confirmation, password reset, receipts, notifications) to this project using the HTTP API described below. Do not add any other email provider, SMTP library or SDK, and do not change the project's existing style, framework or dependency manager beyond what this task needs. ## The API - Base URL: https://send.tsmart.ai - Auth: `Authorization: Bearer ` on every request. Keys look like `ts_xxxxxxxx_…`. - Bodies and responses are JSON. Errors always come back as `{ "statusCode": , "name": "", "message": "" }`. - Lists answer `{ "object": "list", "has_more": , "data": [...] }` and take `limit` (1-100, default 20) with `after` or `before` (an object id). ### Send one email — POST /emails ```http POST https://send.tsmart.ai/emails Authorization: Bearer Content-Type: application/json Idempotency-Key: signup-welcome/ # optional but recommended, 1-256 chars, remembered 24h { "from": "Your Product ", "to": ["person@example.com"], // up to 50 recipients "subject": "Welcome", "html": "

Hello

", // html or text (or both) is required "text": "Hello", "reply_to": "support@your-domain.com", // optional, string or array "cc": [], "bcc": [], // optional "tags": [{ "name": "category", "value": "welcome" }], // optional, for filtering in the dashboard "scheduled_at": "in 10 min", // optional: ISO 8601 or a plain phrase like "in 1 hour" "attachments": [{ "filename": "receipt.pdf", "content": "", "content_type": "application/pdf" }] } ``` Answer: `201 { "id": "" }`. Keep that id — every later call about this message uses it. ### The rest | Call | What it does | |---|---| | `POST /emails/batch` | Up to 100 messages in one request (no attachments). Header `x-batch-validation: strict` rejects the whole batch on the first bad entry; `permissive` sends the good ones. | | `GET /emails`, `GET /emails/{id}` | List and read sent messages (bodies are not stored). | | `GET /emails/{id}/events` | What happened to one message, oldest first. | | `PATCH /emails/{id}`, `POST /emails/{id}/cancel` | Reschedule or cancel a message that has not gone out yet. | | `GET /emails/metrics` | Counts and rates per period or domain. | | `GET/POST /domains`, `POST /domains/{id}/verify` | Add a sending domain and check its DNS. | | `GET/POST /suppressions`, `DELETE /suppressions/{id-or-email}` | The do-not-send list. Hard bounces and spam complaints land here automatically. | | `GET/POST /webhooks` | Delivery events pushed to your endpoint. | | `GET /logs` | Request log, 30 days. | Event names: `scheduled, sent, delivered, delivery_delayed, bounced, complained, opened, clicked, failed, suppressed, canceled`. ### Webhooks (only if this project needs delivery state) Register an endpoint with `POST /webhooks` and verify every delivery before trusting it: the request carries `webhook-id`, `webhook-timestamp` and `webhook-signature`. The signature is `base64(HMAC_SHA256(secret, ".."))`, where `secret` is the part of the signing secret after `whsec_`, base64-decoded. Compare in constant time, reject a timestamp more than five minutes old, and answer 2xx quickly — the sender retries at 5s, 5min, 30min, 2h, 5h, 10h, 10h. ## Rules you must follow 1. **The key comes from the environment**, never from source, a config file in the repository or this prompt. Use `TSMART_API_KEY` unless the project already has a naming convention for secrets; add it to `.env.example` (empty) and to the deployment notes, and make sure `.env` is git-ignored. 2. **The From: address must be on a domain this account has verified**, otherwise the API answers 403 `domain_not_verified`. Use `no-reply@your-domain.com` unless the project already has a sender address on `your-domain.com`. Set `reply_to` to an address a human reads. 3. **Send one email per request with a stable `Idempotency-Key`** when the call can be retried (queue worker, webhook handler, user-triggered action). Same key plus same body inside 24h returns the first result instead of sending twice; same key with a different body is a 409. 4. **Never block a user-facing request on sending.** Put the call behind the project's existing queue or background task if it has one; otherwise fire it after the response is produced and log failures. 5. **Handle the documented errors**, do not swallow them: 401/403 (key or domain), 422 (body), 409 (idempotency), 429 (rate limit — honour `retry-after`; the limit is 10 requests/second per account plus an hourly recipient quota), 502 (mail server refused). Log `name` and `message`; never log the key. 6. **No fallback sender.** If the API refuses the message, report it. Retrying from some other address makes mail arrive from a domain the recipient does not recognise, which is worse than mail that did not go out. 7. **Respect the suppression list.** A recipient that bounced or complained is dropped by the API; treat a `suppressed` event as a permanent "do not send", not a transient failure. 8. **Keep content plain and accessible**: a text part next to the html, no remote scripts, no tracking pixels the project did not ask for. ## Helper packages (optional) If the project is Node or Python and you prefer a typed client over `fetch`: ```bash npm install https://tsmart.ai/sdk/node/tsmart-sdk-1.1.1.tgz pip install https://tsmart.ai/sdk/python/tsmart-1.1.1-py3-none-any.whl ``` ```js import { Tsmart } from "@tsmart/sdk"; const tsmart = new Tsmart(); // reads TSMART_API_KEY const { data, error } = await tsmart.emails.send({ from: "Your Product ", to: ["person@example.com"], subject: "Welcome", html: "

Hello

" }); ``` A command line tool is available too (`npm install -g https://tsmart.ai/cli/tsmart-cli-1.1.0.tgz`), useful for a smoke test before you write code. Clients written for the common Bearer-key email API style work unchanged when their base URL is set to `https://send.tsmart.ai`. ## What I expect from you 1. Find where this project already sends email (search for existing mail code, templates and queues) and either replace that transport or add one if there is none. Report what you found before changing it. 2. Implement a small module with one function per message type the project needs, not a call scattered in handlers. Reuse the project's logging, error types and test style. 3. Add the environment variable, the example file entry and a line in the project's README or deployment doc. 4. Write one test that proves the payload is built correctly and that a 4xx is surfaced rather than swallowed. Do not call the live API from tests. 5. Tell me what to set in production and how to verify it: one real test message, where to see its state, and what to check if it does not arrive. Full reference: https://tsmart.ai/docs