const { data, error } = await client.emails.send({ from: "sender@yourdomain.com", from_name: "Your App", to: ["recipient@example.com"], subject: "Hello from Lettr", html: "<h1>Welcome!</h1><p>Thanks for signing up.</p>",});if (error) { console.error(error.message); return;}console.log(data.request_id); // unique id for trackingconsole.log(data.accepted); // number of accepted recipientsconsole.log(data.rejected); // number of rejected recipients
A send requires from, to, subject, and at least one of html, text, or template_slug. The full recipient cap is 50 addresses across to/cc/bcc.
await client.emails.send({ from: "sender@yourdomain.com", to: ["recipient@example.com"], subject: "Plain text update", text: "This is a plain text email.", // include both html and text for a multipart message});
When sending a template, subject is optional — the template’s own subject is used unless you override it. The substitution_data keys map to merge tags (e.g. {{name}}):
await client.emails.send({ from: "sender@yourdomain.com", to: ["customer@example.com"], template_slug: "welcome", substitution_data: { name: "John" }, template_version: 2, // optional: pin a version});
See Templates for managing templates programmatically.
The SDK never throws for API or network errors — it returns them in error. Discriminate on error.type first, then optionally on error.error_code:
const { data, error } = await client.emails.send({ /* ... */ });if (error) { switch (error.type) { case "validation": // 422 — invalid request. Field validation populates error.errors // (a field → messages map); precondition 422s (e.g. campaign_not_sendable) // leave it empty and carry error.error_code instead. if (Object.keys(error.errors).length > 0) { console.error("Field errors:", error.errors); } else { console.error("Precondition failed:", error.error_code); } break; case "api": // Other API errors. error.error_code is always present // (e.g. "unauthorized", "quota_exceeded", "not_found"). console.error("API error:", error.error_code, error.message); break; case "network": // Connection failure, timeout, DNS — no response received. console.error("Network error:", error.message); break; } return;}
Always handle error before reading data. TypeScript narrows data to non-null only inside the if (!error) branch — reading data without the check leaves it typed as possibly null.