TypeScript SDK
The postfleet package is a typed client for the /api/v1 REST API. Responses come back verbatim — no stripping, no synthetic wrappers. Node 18+ (global fetch).
Install
npm install postfleet
A pf_ API key from https://postfleet.ai. Override the origin with baseUrl for staging or self-hosted.
import { Postfleet } from 'postfleet';
const pf = new Postfleet({ apiKey: process.env.POSTFLEET_API_KEY! });
Quickstart
Create a mailbox, wait for a reply, then send in-thread:
const box = await pf.mailboxes.create({ display_name: 'Support agent' });
const incoming = await pf.messages.waitFor({ mailbox_id: box.id });
if (!incoming) throw new Error('timed out waiting for mail');
// Read body_clean: the sanitized text. body_raw is the pre-sanitization original,
// kept for audit — the exact surface the screening pipeline stripped.
console.log(incoming.body_clean);
await pf.messages.send({
mailbox_id: box.id,
reply_to_message_id: incoming.id,
text: 'Got it — working on this now.',
client_id: 'reply-1',
});
Resources
| Method | Endpoint |
|---|---|
pf.mailboxes.create |
POST /api/v1/mailboxes |
pf.mailboxes.list |
GET /api/v1/mailboxes |
pf.domains.list |
GET /api/v1/domains |
pf.domains.create |
POST /api/v1/domains |
pf.domains.verify |
POST /api/v1/domains/{id}/verify |
pf.messages.list |
GET /api/v1/messages?mailbox_id&limit |
pf.messages.get |
GET /api/v1/messages/{id} |
pf.messages.send |
POST /api/v1/send |
pf.messages.waitFor |
polls GET /api/v1/messages then GET /api/v1/messages/{id} |
pf.drafts.create |
POST /api/v1/drafts |
pf.drafts.list |
GET /api/v1/drafts?mailbox_id |
pf.drafts.get |
GET /api/v1/drafts/{id} |
pf.drafts.update |
PATCH /api/v1/drafts/{id} |
pf.drafts.delete |
DELETE /api/v1/drafts/{id} |
pf.drafts.send |
POST /api/v1/drafts/{id}/send |
messages.send and drafts.send return the sent shape on 200/201, or { draft_id, status: "pending_approval" } on 202 when the mailbox requires a human. That is a success — do not retry. See Drafts & human-in-the-loop.
waitFor
pf.messages.waitFor({ mailbox_id, from_contains?, subject_contains?, timeout_ms?, poll_interval_ms? }) polls the inbox until a matching inbound message arrives whose created_at is at or after the call started. Optional from_contains / subject_contains are case-insensitive substrings. Defaults: timeout 60 000 ms, poll every 2 000 ms. Returns the full message on a hit, or null on timeout.
Errors
Non-2xx responses and network failures throw PostfleetError:
status— HTTP status, or0whenfetchitself throwscode— APIcodewhen present (key_scope,delivery_outcome_unknown, …)retryable— APIretryablewhen present;trueon network failurebody— parsed JSON, or{}if the body was not JSON
import { PostfleetError } from 'postfleet';
try {
await pf.messages.send({ mailbox_id, to, subject, text, client_id });
} catch (err) {
if (err instanceof PostfleetError && err.retryable) {
// retry with the SAME client_id
}
throw err;
}
Idempotency
client_id stays optional and the SDK never mints one. Generate a client_id once per logical send and reuse it on retry; never mint a new one per attempt. A fresh key per attempt is exactly the double-send the key prevents. See Sending & idempotency.
Not in v0.1
No retries with backoff, and no Python SDK. Both are later, separate decisions.
The full request and response shapes live in the API reference.