Documentation Index
Fetch the complete documentation index at: https://drin.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
The official SDK is published to npm as drin.
It’s a thin, typed wrapper over the REST API with built-in retries, cursor
pagination, typed errors, and webhook signature verification.
Requires Node.js 20+ and (for types) TypeScript 5.6+.
Install
Create a client
import { DrinClient } from "drin";
const drin = new DrinClient({
apiKey: process.env.DRIN_API_KEY!, // required
sender: "my-project", // only for account-wide keys
// baseUrl: "https://api.drin.run", // default
// timeoutMs: 30_000, // per-request timeout (0 = off)
// maxRetries: 2, // transient-failure retries
});
The client exposes one resource per area of the API:
emails · domains · inboxes · threads · inbound · webhooks ·
suppressions · apiKeys · metrics · templates · contacts
Send
const { id } = await drin.emails.send({
from: { email: "hello@acme.com", name: "Acme" },
to: [{ email: "customer@example.com" }],
subject: "Welcome aboard",
html: "<h1>You're in 🎉</h1>",
text: "You're in",
});
Pick a verified sending domain in code with the domains.listVerified()
convenience:
const [domain] = await drin.domains.listVerified();
await drin.emails.send({
from: { email: `hello@${domain.domain}` },
to: [{ email: "customer@example.com" }],
subject: "Hi",
html: "<p>…</p>",
});
Paginate
Every list endpoint returns { data, nextCursor }. Use .paginate() for an
async iterator that walks every page for you:
for await (const message of drin.emails.paginate({ status: "bounced" })) {
console.log(message.to, message.subject);
}
Receive & reply
// One-call threaded reply — subject, To, and threading headers are set for you.
await drin.emails.reply(inboundMessageId, {
html: "<p>Thanks for reaching out — we're on it.</p>",
});
Verify a webhook
webhooks.verify() is a pure, local check — pass the raw request body and
the Drin-Signature header. It throws on any mismatch.
import { DrinWebhookVerificationError } from "drin";
app.post("/webhooks/drin", async (req) => {
try {
const event = drin.webhooks.verify(
req.rawBody, // the exact bytes received
req.headers["drin-signature"],
process.env.DRIN_WEBHOOK_SECRET!,
);
// event is the typed, verified payload
} catch (err) {
if (err instanceof DrinWebhookVerificationError) return res.status(400).end();
throw err;
}
});
Handle errors
Every failure is a typed subclass of DrinError — branch on instanceof or
err.type. See Errors for the full list.
import { DrinRateLimitError, DrinSuppressedError } from "drin";
React Email
If you author emails with React Email, render and send in
one step with the optional helper:
import { sendReactEmail } from "drin/react-email";
await sendReactEmail(drin, {
from: { email: "hello@acme.com" },
to: [{ email: "customer@example.com" }],
subject: "Welcome",
react: <WelcomeEmail name="Sam" />,
});