Skip to Content
SDKsNode

Node SDK

local-letter on npm. Renders a template against your API, then hands the result to Resend  from your own process.

npm install local-letter

Ships its own TypeScript types — there’s no @types package.

Quick start

import { TemplateClient } from 'local-letter' const letters = new TemplateClient({ baseUrl: 'https://letters.yourcompany.com', apiKey: process.env.LOCAL_LETTER_API_KEY!, resendApiKey: process.env.RESEND_API_KEY!, from: 'hello@yourcompany.com', }) const result = await letters.send({ template: 'welcome-email', to: 'customer@example.com', variables: { first_name: 'Sagar' }, }) console.log(result.id) // Resend message id

Build one client per process and reuse it — the constructor creates a Resend client, so a client per request is pure overhead.

Localisation

Pass the recipient’s locale and something to fall back to:

const result = await letters.send({ template: 'welcome-email', to: user.email, variables: { first_name: user.firstName }, locale: user.locale, // "fr" — the French version, if it exists fallbackLocale: 'en', // otherwise English, then the template default }) result.locale // what actually went out

Casing doesn’t matter: en-us and EN-US both resolve to en-US. Full rules in Locales & fallback.

new TemplateClient(options)

interface TemplateClientOptions { baseUrl: string apiKey: string resendApiKey: string from: string }
OptionTypeNotes
baseUrlstringYour Local Letter API, e.g. https://letters.yourcompany.com. No trailing slash.
apiKeystringProject API key, from the dashboard’s API Keys page.
resendApiKeystringPassed to Resend only. Local Letter never receives it.
fromstringDefault sender for every send().

letters.send(options)

interface SendOptions { template: string to: string | string[] variables?: Record<string, unknown> locale?: string fallbackLocale?: string from?: string replyTo?: string }
OptionTypeNotes
templatestringTemplate key, e.g. "welcome-email".
tostring | string[]One or more recipients.
variablesRecord<string, unknown>Values for the template’s {{tokens}}.
localestringPreferred locale.
fallbackLocalestringUsed when locale has no translation.
fromstringOverrides the client default, this send only.
replyTostringReply-to address.

Resolves to a SendResult:

interface SendResult { id: string // Resend message id subject: string // rendered subject html: string // rendered body locale: string // the locale that actually shipped }

Errors

Two error classes, so you can tell a template problem from a delivery problem:

import { TemplateRenderError, TemplateSendError } from 'local-letter' try { await letters.send({ template: 'welcome-email', to: user.email }) } catch (err) { if (err instanceof TemplateRenderError) { // Your API rejected the render. err.status: // 401 bad key · 403 key not linked to a project · 404 no such template } else if (err instanceof TemplateSendError) { // Rendered fine, Resend refused it — often an unverified sender domain. // err.cause holds Resend's own error object. } else { // Nothing reached the API — wrong baseUrl, or it isn't running. } }

A missing variable is not an error. An unmatched {{token}} is left in the rendered output rather than blanked — see Variables.

Under the hood

send() is two calls:

  1. POST {baseUrl}/v1/render/{template} with Authorization: Bearer {apiKey}, carrying variables, locale and fallbackLocale.
  2. resend.emails.send(...) with the rendered subject and HTML.

Your Resend key never touches Local Letter, and Local Letter never sends mail. If you’d rather render and deliver separately, call the render endpoint yourself.

Example app

A runnable Node service using the SDK end to end lives in examples/node.

Last updated on