Skip to content
Local Letter Cloud is coming soon
Open source · Self-hosted · Multi-language

Transactional email
that speaks every language

Design a template once in a visual editor, translate it per locale, and render it from any codebase with one typed SDK call. Your templates, your database, your infrastructure.

  • No vendor lock-in
  • Runs in your VPC
  • MIT licensed
localletter.acme.internal / templates / welcome-email

Variables

{{first_name}}string*
{{workspace_url}}url*
{{trial_days}}number

Fallback chain

enendefault

Serving the published “en” variant.

Welcome to Acme, Sarah

Acme <hello@acme.com> · to sarah@example.com

Your workspace is ready

Hi Sarah,

Your workspace is ready. Invite your team and send your first campaign in under five minutes.

Open workspace

Sent by Acme · You can unsubscribe at any time

Drops into the stack you already run

Node.jsTypeScriptPostgreSQLPrismaExpressReactResendDockerGrapesJSNode.jsTypeScriptPostgreSQLPrismaExpressReactResendDockerGrapesJS

The problem

Email templates rot in string literals

Transactional email starts as one hardcoded HTML blob and ends as a maintenance problem that nobody owns. Adding a second language usually makes it worse.

Without Local Letter

  • Email HTML pasted into template literals across three services
  • Every locale is a new if (locale === "fr") branch
  • Marketing files a ticket to change one button colour
  • A missing variable ships as literal {{first_name}} to 40,000 inboxes

With Local Letter

  • One template key, one visual editor, every locale in one place
  • The SDK resolves the locale and falls back automatically
  • Non-engineers publish copy changes without a deploy
  • Required variables are validated before a single email leaves

Everything in the box

A whole email pipeline,
minus the SaaS bill

Drag-and-drop email editor

A GrapesJS newsletter editor that outputs table-based, inlined HTML every email client can actually render. Designers ship without touching a build.

A locale is a variant, not a fork

Every template holds one variant per locale with its own subject, body and design. Request ja, get ja — or the fallback you configured.

Typed variable schemas

Declare {{first_name}} as a required string and the render call rejects a payload that omits it — before it reaches an inbox.

Draft, publish, roll back

Edit safely in draft, publish when it is right, and snapshot every version so a bad copy change is one click away from undone.

Scoped API keys

One key per project, hashed at rest, shown exactly once. Keys read only their own project, and revoking is instant.

Runs where you run

Docker Compose, your Postgres, your network. No outbound call leaves your perimeter to render a template.

Your secrets stay yours

The SDK talks to your mail provider directly. Local Letter never sees, stores or proxies your provider API key.

How it works

Four steps from blank page
to delivered inbox

01Dashboard

Design it once

Build the template in the visual editor, drop in {{variables}}, and declare which ones are required.

02Locales

Translate per locale

Add a variant for each language. Subject, body and layout can differ — the template key never changes.

03Versioning

Publish

Flip the locale from draft to published. Versions are snapshotted, so rolling back is one click.

04SDK

Send from anywhere

Your service calls the SDK with a key, a locale and variables. Local Letter renders, your provider delivers.

One key, every language

Switch the locale,
not the code path

Your application always asks for the same template key. Local Letter picks the published variant for the requested locale, and walks your fallback chain when that language has not been translated yet.

Requesting locale “en” returns this published variant directly.

POST /v1/render/welcome-email · locale=en

Welcome to Acme, Sarah

Acme <hello@acme.com> · to sarah@example.com

Your workspace is ready

Hi Sarah,

Your workspace is ready. Invite your team and send your first campaign in under five minutes.

Open workspace

Sent by Acme · You can unsubscribe at any time

Response

{
  "locale":  "en",
  "subject": "Welcome to Acme, {{first_name}}",
  "html":    "<!doctype html>…"
}

Built for your codebase

One call.
Any service.

The Node SDK is shipping today: it renders the template against your instance and hands the result to your mail provider. Everything is a plain authenticated HTTP call underneath, so any language can talk to it right now.

Typed errors. Render failures and send failures stay distinguishable.

Locale fallback. Ask for ja, get ja — or the chain you configured.

No mailer lock-in. Render-only mode hands you subject + HTML to send yourself.

SDKs for other languages are coming

Python, Go, Ruby and PHP clients are in progress. Tell us which one you need first and we will prioritise it.

Request a language
pnpm add local-letter
import { TemplateClient } from "local-letter";

const letters = new TemplateClient({
  baseUrl: process.env.LOCAL_LETTER_URL,
  apiKey: process.env.LOCAL_LETTER_API_KEY,
  resendApiKey: process.env.RESEND_API_KEY,
  from: "hello@acme.com",
});

// Renders the published "fr" variant, then sends it.
const { id, subject } = await letters.send({
  template: "welcome-email",
  to: "sarah@example.com",
  locale: "fr",
  fallbackLocale: "en",
  variables: { first_name: "Sarah" },
});

Self-hosted by default

The data stays
on your side of the wall

Local Letter is a Docker Compose stack you run yourself: an Express API, a React dashboard and a Postgres database. Self-hosting is free and always will be — Cloud, when it lands, is something you opt into, not something you get moved onto.

Your Postgres

Templates, locales and versions live in a database you already back up and audit.

Your network

Render calls stay inside your VPC. There is no Local Letter cloud to route through.

Your provider keys

The SDK calls Resend directly with your key. Our API never receives or stores it.

Your compliance story

Customer names and addresses never leave the jurisdiction you deployed into.

docker-compose.yml
services:
  api:
    image: ghcr.io/local-letter/api:latest
    environment:
      DATABASE_URL: postgres://letter:pw@db:5432/letter
      WEB_ORIGIN: https://letters.acme.internal
    ports: ["4000:4000"]

  web:
    image: ghcr.io/local-letter/web:latest
    environment:
      VITE_API_URL: https://api.letters.acme.internal
    ports: ["5173:5173"]

  db:
    image: postgres:16
    volumes: ["letter-data:/var/lib/postgresql/data"]
$ docker compose up -d
Documentation coming soon

The manual is
nearly written

We are finishing the deployment guides, the SDK reference and the API docs. Until they publish, our team will onboard you directly — no waiting on a changelog.

Get onboarded now
  • Deploying with Docker ComposeDrafting
  • Modelling templates and localesDrafting
  • Node SDK referenceIn review
  • HTTP API referenceIn review
  • Variable schemas and validationPlanned
  • Versioning and rollbackPlanned

Questions

Before you
deploy it

It can, and it does not have to. The Node SDK will render a template and hand it to Resend in a single call, or return the subject and HTML so you can send it with whatever mailer you already run. Local Letter itself never holds an SMTP connection.

Get started

Let's scope your
email pipeline

Tell us how many locales you send in and where you need it deployed. We will walk your team through the stack and put together a plan that fits.

Free to self-host · MIT licensed · Cloud coming soon