@productcraft/envoi
v0.0.5
Published
Receive-and-store mail platform — template-rendered sends, mailboxes + domains + DKIM, message timeline, suppression lists, outbound webhooks via ProductCraft Envoi. Generated from the production OpenAPI spec.
Maintainers
Readme
@productcraft/envoi
Typed Node.js SDK for ProductCraft Envoi — receive-and-store mail platform: send template-rendered messages, manage mailboxes + domains + DKIM, lint templates, track deliveries, configure outbound webhooks, hold suppression lists.
npm install @productcraft/envoiServer-side only. The SDK ships a Platform API Key in the Authorization header — never embed it in a browser bundle.
Quick start
import { Envoi } from "@productcraft/envoi";
const envoi = new Envoi({
auth: { type: "apiKey", key: process.env.PCFT_KEY! },
});
// Send a template-rendered message
const { data, error } = await envoi.client.POST(
"/v1/workspaces/{workspace_id}/templates/{name}/send",
{
params: {
path: { workspace_id: "ws_...", name: "welcome" },
// Make retries safe — replays return the original response
// with `Idempotent-Replay: true` for 24h.
header: { "Idempotency-Key": "welcome-2026-05-22-alice" },
},
body: {
from: "[email protected]",
to: "[email protected]",
subject: "Welcome", // optional — falls back to template subject
data: { name: "Alice" }, // rendered into the template
},
},
);The client property is an openapi-fetch instance bound to https://api.mail.productcraft.co and your auth credential. Every endpoint in the published OpenAPI spec is reachable through it — your editor's autocomplete lists paths + method shapes + body fields.
Configuration
new Envoi({
// The auth credential the SDK presents to Envoi
auth: { type: "apiKey", key: "pcft_live_..." }
| { type: "bearer", token: "eyJ..." }
| { type: "cookie", value: "auth_token=..." },
// Override the prod base URL — useful for staging / a local proxy
baseUrl: "https://api.mail.example.test",
// Custom fetch implementation (undici with retry, mock in tests, ...)
fetch: customFetch,
});Common operations
Every path is workspace-scoped — {workspace_id} is the UUID of the workspace that owns the resource, returned by @productcraft/platform-auth's introspect endpoint.
Templates
// List templates
await envoi.client.GET(
"/v1/workspaces/{workspace_id}/templates",
{ params: { path: { workspace_id: "ws_..." } } },
);
// Lint a template before saving — the body field is `body_html`
// (wire) / `bodyHtml` (camelCase TS DTO), NOT `html`.
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/templates/lint",
{
params: { path: { workspace_id: "<workspace-uuid>" } },
body: { body_html: "<p>Hello {{name}}</p>" },
},
);
// → { score, findings: [...] }
// Preview / render with sample data
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/templates/{name}/render",
{
params: { path: { workspace_id: "ws_...", name: "welcome" } },
body: { data: { name: "Alice" } },
},
);
// Send to one address (template-rendered)
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/templates/{name}/send",
{ params: { ... }, body: { from, to, data } },
);
// Send the same template to many addresses (batch)
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/templates/{name}/send-batch",
{ params: { ... }, body: { recipients: [{ to, data }, ...] } },
);Domains + DKIM
// List domains
await envoi.client.GET("/v1/workspaces/{workspace_id}/domains", { ... });
// Add a domain (then publish the DKIM TXT record Envoi prints)
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/domains",
{ params: { ... }, body: { name: "mail.yourbrand.com" } },
);
// Verify after DNS propagation
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/domains/{domain_id}/verify",
{ params: { path: { workspace_id, domain_id } } },
);Messages
// Workspace-wide message listing + filters
await envoi.client.GET(
"/v1/workspaces/{workspace_id}/messages",
{ params: { path: { workspace_id }, query: { limit: 50 } } },
);
// Read body / attachments / raw RFC822
await envoi.client.GET("/v1/workspaces/{workspace_id}/messages/{id}/body", { ... });
await envoi.client.GET(
"/v1/workspaces/{workspace_id}/mailboxes/{id}/messages/{mid}/attachments/{position}",
{ ... },
);Suppression list
// Add an email to the workspace suppression list
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/suppression",
{ params: { ... }, body: { email: "[email protected]", reason: "hard_bounce" } },
);Outbound webhooks
// Configure the workspace's default webhook (delivery / open / bounce events)
await envoi.client.PUT(
"/v1/workspaces/{workspace_id}/webhooks/default",
{
params: { ... },
body: { url: "https://yourapp.com/hooks/envoi", events: ["message.delivered"] },
},
);
// Per-domain webhook overrides + secret rotation
await envoi.client.POST(
"/v1/workspaces/{workspace_id}/webhooks/domains/{domain_id}/rotate-secret",
{ ... },
);Idempotency
Send endpoints accept an Idempotency-Key header (1–256 chars, [A-Za-z0-9_\-:]). Retries with the same key + same body replay the original response with Idempotent-Replay: true for 24h. Same key + different body returns 409 IDEMPOTENCY_KEY_REUSE.
How this SDK is built
Generated from the live OpenAPI spec at https://api.mail.productcraft.co/docs-json via openapi-typescript (types) + openapi-fetch (runtime). The nightly spec-refresh workflow opens a PR whenever the spec changes; merging publishes a patch bump so type-safe consumers stay current automatically.
License
MIT.
