Letterpier
Project- Messages
- Domains
- API keys
- Webhooks
- Suppressions
Send, receive, and follow every message from one calm workspace. Letterpier answers the Resend HTTP API for the endpoints it covers, so the Resend Node SDK works with a Letterpier key and one extra option: baseUrl.
Already have access? Sign in
Developer preview · Access by request only · Tested with resend@6.30.0 · See what’s covered
Simulation of the message lifecycle. Outbound: your code calls POST /emails; the message is queued, handed to Postal, sent, and delivered when the recipient’s server accepts it; a signed email.delivered webhook follows. Messages can also be delayed, which Postal retries, or bounced, which adds the address to the suppression list. Inbound: mail to any address on a verified domain with receiving on arrives at Letterpier’s MX, is stored encrypted, triggers an email.received webhook, and your app fetches it with a full-access live key. This is an illustration, not live traffic.
const mail = new Resend(process.env.LETTERPIER_API_KEY, { baseUrl: 'https://app.letterpier.com',});await mail.emails.send({ from, to, subject, text });200 · queued, not delivered
Select a station to see what happens there.
Your code calls the API with a Letterpier key. Letterpier validates the request and stores the message before it answers.
Accepted by the API and waiting for the worker. This is what a 200 response means: queued, not delivered.
The worker hands the message to Postal, Letterpier’s mail server. If the hand-off is interrupted, the message becomes Unknown and is never resent blindly.
Postal accepted the message and is delivering it.
The recipient’s mail server accepted it. That isn’t a promise of the inbox, and a bounce can still follow.
Letterpier sends a signed email.delivered event to your endpoint and retries up to 8 attempts over about 27½ hours.
The receiving server deferred the message. Postal keeps retrying.
The receiving side refused it. The address joins this project’s suppression list.
Mail for any address on a verified domain with receiving on arrives at Letterpier’s mail server. Routing uses the SMTP envelope recipient, not the visible To header.
The message is stored with its body, attachments and original .eml encrypted at rest.
An email.received event tells your app that a message arrived.
Your app fetches the body and attachments with a full-access live key. Download links expire after 15 minutes.
Letterpier carries mail for its operator’s own products today. Other teams can request access; each request is reviewed by hand. Request access
Letterpier implements the parts of the Resend HTTP API that transactional products rely on. Point the unmodified Resend Node SDK at Letterpier and your send calls keep their shape.
lp_test_ capture messages in the sandbox; keys starting with lp_live_ deliver.import { Resend } from 'resend';const mail = new Resend(process.env.LETTERPIER_API_KEY, { baseUrl: 'https://app.letterpier.com',});const { data, error } = await mail.emails.send({ from: 'My product <hello@your-verified-domain.com>', to: 'customer@example.com', subject: 'Welcome', text: 'Your account is ready.',}, { idempotencyKey: 'welcome/customer-123' });Also supported: batches of up to 100 messages, accepted all together or not at all · scheduling up to 30 days ahead, with reschedule and cancel while a message is queued · 50 recipients and 20 attachments per message · about 10 MB of attachments per request.
Tested with resend@6.30.0 for sending, receiving, domains, API keys and webhooks. Templates, contacts, broadcasts, audiences and other marketing APIs aren’t part of Letterpier; calls to them return an explicit error. See the compatibility table. Letterpier is not affiliated with Resend.
Every project has sandbox and live keys. Sandbox messages are captured and never delivered, and your webhooks still receive synthetic events marked sandbox: true, so you can build the whole flow first. Live keys hand mail to Letterpier’s own Postal server for delivery.
lp_test_…
Captured · never delivered
lp_live_…
Recipient’s server
Delivered through Postal
lp_test_…
Captured · never delivered
lp_live_…
Recipient’s server
Delivered through Postal
| Key type | Sending only | Full access |
|---|---|---|
| Sandbox | Send, batch and schedule. Captured, never delivered. | Also read, reschedule and cancel messages, and manage domains, keys and webhooks. |
| Live | Send, batch and schedule. Delivered through Postal. | Also read, reschedule and cancel messages, read attachments, retrieve received mail, and manage domains, keys and webhooks. |
Keys are stored as hashes and shown exactly once.
Once a domain is verified with receiving on, mail to any address on it (support@, invoices@, anything@) lands in that domain’s project. Your app gets an email.received webhook, then fetches the body, the attachments and the original .eml with a full-access live key.
const { data: email } = await mail.emails.receiving.get(event.data.email_id);Add a domain or a subdomain and Letterpier generates what it needs: an ownership TXT record, a 2048-bit RSA DKIM key, an SPF include, a return-path CNAME and a DMARC record, plus an MX record if the domain also receives mail. Then it checks them, and keeps checking.
Example domain · simulated
| Purpose | Type | Name | Value | Status |
|---|---|---|---|---|
| Ownership | TXT | _letterpier-verification.example.com | letterpier-verification=4kR2PXsAcYYZpDKIYxqo6Nmc4VlFoEvAEYAhb_2ce8M | Not checkedVerified |
| DKIM | TXT | lp-mun3p042._domainkey.example.com | v=DKIM1; t=s; h=sha256; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB…Show full keyHide full keyv=DKIM1; t=s; h=sha256; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1eyP4OlEy30qwWLHT22UhA+2z8NWS/doZyomjiSsd72RXjVfRGAqhgoqzsfGZidYZ7Du4WfukcKCLNKB4tNaSkjJLdecHjdkBj3PE6JF9WThHGrKiQeIoN7EUmn9TwFU3nhn+VhudEoQ5H0UZd/WOqtTDUyCwMCj9OlD3bSjYRoUgI8TIJBkV8d6+zFThtWLHkmmfA9hkqDmZLTHa7dRcoPqiXXcNtKLxMiUXo1xQyFW6Ez9Aag7ZAxv5PJ43LX26iGoiaglZDW3703bIouvRTo/Pa7kvCiBhmFY3UaPooPswiwVFID7b0urlSmwkmvooNnOs2S9gEX5dCxJzcaaNQIDAQAB; | Not checkedVerified |
| SPF | TXT | example.com | v=spf1 include:spf.letterpier.com -all | Not checkedVerified |
| Return path | CNAME | psrp.example.com | rp.letterpier.com | Not checkedVerified |
| Receiving | MX | example.com | 10 mail.letterpier.com | Not checkedVerified |
| DMARC | TXT | _dmarc.example.com | v=DMARC1; p=none; | Not checkedVerified |
6 of 6 records verifiedNot checked yet
Search by recipient, sender or subject. Open any message to read its body, download its attachments and see what happened to it, in order.
Select a status to read what it means.
The recipient’s mail server accepted it. That isn’t a promise of the inbox, and a bounce can still follow.
Solid: settled.
Dashed: the story isn’t over. Solid: settled. Hatched: not real mail.
If a hand-off to Postal is interrupted, Letterpier never resends blindly. Check Postal acceptance looks the message up and reconciles it.
Addresses that bounce or fail permanently join the project’s suppression list, so the next send doesn’t repeat the mistake. You decide when to allow them again.
| Direction | Recipient / subject / status | Status | When |
|---|---|---|---|
| Outbound | Outbound · 2 min agoada@ | Delivered | |
| Inbound | Inbound · 5 min agosupport@ | Received | |
| Outbound | Outbound · 12 min agograce@ | Unknown | |
| Outbound | Outbound · 20 min agotest@ | Sandbox |
Delivered means a receiving server accepted the message. It isn’t a promise of the inbox, and a bounce can still follow.
Events carry Svix-format signatures (svix-id, svix-timestamp, svix-signature), so the Resend SDK’s webhooks.verify checks them unchanged. If your endpoint doesn’t answer, Letterpier tries again after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours: eight attempts over about 27½ hours. After that, replay any delivery from the dashboard while it’s retained.
| Attempt | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| Waits before it | – | 5 s | 5 min | 30 min | 2 h | 5 h | 10 h | 10 h |
| Time since the first (rounded) | 0 | 5 s | 5 min | 35 min | 2 h 35 min | 7 h 35 min | 17 h 35 min | 27 h 35 min |
| Attempt | Waits before it | Time since the first (rounded) |
|---|---|---|
| 1 | – | 0 |
| 2 | 5 s | 5 s |
| 3 | 5 min | 5 min |
| 4 | 30 min | 35 min |
| 5 | 2 h | 2 h 35 min |
| 6 | 5 h | 7 h 35 min |
| 7 | 10 h | 17 h 35 min |
| 8 | 10 h | 27 h 35 min |
Headers of attempt 4 of 8
200 · deliveredDelivery is at-least-once: deduplicate on svix-id.
Organisations hold projects, and each project holds its own messages, domains, API keys, webhooks and suppressions. Nothing crosses between them, and each project gets its own Postal server credentials.
API keys are stored as hashes and shown exactly once. Each key is sandbox or live, and sending-only or full access.
We’d rather be exact than impressive.
This is encryption at rest with keys the application manages. It is not end-to-end encryption.
| Data | At rest | Why |
|---|---|---|
| Message bodies and outbound attachments | Encrypted, with keys the application manages | |
| Received attachments and original .eml files | Encrypted, on a private volume | |
| DKIM private keys (Letterpier’s copy) | Encrypted | Postal keeps its own copy in plaintext to sign mail. |
| Webhook signing secrets | Encrypted | Shown once. |
| API keys | Stored as SHA-256 hashes | Shown once. |
| Subjects, addresses, event metadata | Plaintext | So you can search your logs and we can route mail. |
| Mail inside Postal, our mail server | Plaintext, in its private database | Postal needs it to queue, sign and deliver. Raw mail is kept 7 days, metadata 30 days. |
Data at rest
Message bodies and outbound attachments
Received attachments and original .eml files
DKIM private keys (Letterpier’s copy)
Webhook signing secrets
API keys
Subjects, addresses, event metadata
Mail inside Postal, our mail server
The web app, the worker, Postal, its database and the encrypted attachment volume run on one OVH server in OVH’s Limburg, Germany data centre in the EU. The application database is Neon PostgreSQL on AWS in Frankfurt (eu-central-1). Vercel only registers our domain and serves its DNS.
| Provider | What it does for Letterpier | Where | Headquarters |
|---|---|---|---|
| OVH | Server for the web app, worker, Postal, MariaDB and the encrypted attachment volume | EU data centre (Limburg, Germany) | France |
| Neon (Databricks, Inc.) | Application PostgreSQL database | AWS eu-central-1, Frankfurt | USA |
| Vercel | Domain registration and DNS for letterpier.com; no message data | None | USA |
| Cloudflare R2 | Supported, not in use | None | USA |
Providers
OVH
Neon (Databricks, Inc.)
Vercel
Cloudflare R2
Neon and Vercel are US-headquartered companies. Data is stored in EU data centres, but EU hosting on its own doesn’t mean EU-only access.
Mail you send travels to your recipients’ mail servers, wherever they are.
Not A newsletter tool: no contacts, lists or campaigns.
Not A tracker: no open or click tracking.
Not Open to everyone: access is by request only.
Not The whole Resend product: only the endpoints listed in the compatibility table.
Letterpier is run by Michael Ketzer in Germany for a small number of products. If yours sends transactional email and needs a calm place for it, tell us about it.
Letterpier is offered to businesses only.