Developer Documentation
Everything you need to integrate OonruMail into your application. TypeScript SDK with full type safety, zero dependencies, and automatic retries.
Quick Start
Start hereInstallation
Install the SDK via your preferred package manager:
npm install @oonrumail/sdkpnpm add @oonrumail/sdkyarn add @oonrumail/sdkConfiguration
Create a client instance with your API key. You can get an API key from the Console → API Keys.
import { OonruMail } from "@oonrumail/sdk";
const mail = new OonruMail({
apiKey: "em_live_...", // Required — your API key
baseUrl: "https://api.oonrumail.com", // Default
timeout: 30000, // Request timeout (ms)
maxRetries: 3, // Retries on 5xx / 429
});The SDK automatically retries failed requests with exponential backoff on 5xx server errors and 429 rate limit responses.
Send your first email
With the client configured, sending an email is just a few lines:
import { OonruMail } from "@oonrumail/sdk";
const mail = new OonruMail({
apiKey: process.env.OONRUMAIL_API_KEY,
});
const result = await mail.send({
from: "noreply@myapp.com",
to: ["user@example.com"],
subject: "Welcome!",
html: "<h1>Welcome aboard</h1>",
});
console.log(result.message_id); // "msg-abc123"Sending Emails
Simple send
Use sendSimple() for a minimal API with fewer required fields:
await mail.sendSimple({
from: "noreply@myapp.com",
to: "user@example.com", // single string or array
subject: "Hello",
html: "<p>Hello world</p>",
});Full options
The send() method accepts all email options including CC, BCC, reply-to, categories, custom arguments, tracking, and attachments:
await mail.send({
from: "noreply@myapp.com",
to: ["user@example.com"],
cc: ["cc@example.com"],
bcc: ["bcc@example.com"],
reply_to: "support@myapp.com",
subject: "Order Confirmed",
html: "<h1>Order #123</h1>",
text: "Order #123 confirmed", // plain text fallback
categories: ["orders"],
custom_args: { order_id: "123" },
track_opens: true,
track_clicks: true,
attachments: [
{
filename: "invoice.pdf",
content: btoa("..."), // base64 encoded
content_type: "application/pdf",
},
],
});Send with template
Send using a pre-built template with variable substitutions. Create templates in the Console → Templates or via the SDK.
await mail.sendWithTemplate({
from: "noreply@myapp.com",
to: "user@example.com",
template_id: "tpl-welcome-v2",
substitutions: {
name: "Alice",
company: "Acme",
},
});Batch send
Send up to 1,000 emails in a single API call. Each message can have different recipients, subjects, and content:
const result = await mail.sendBatch({
messages: [
{ from: "noreply@app.com", to: ["a@b.com"], subject: "Hi A", html: "..." },
{ from: "noreply@app.com", to: ["c@d.com"], subject: "Hi C", html: "..." },
],
});
console.log(`Queued: ${result.total_queued}`);Scheduled send
Schedule emails for future delivery by including the send_at field with an ISO 8601 timestamp:
await mail.send({
from: "noreply@app.com",
to: ["user@example.com"],
subject: "Reminder",
html: "<p>Don't forget!</p>",
send_at: "2026-03-01T09:00:00Z",
});Templates
Create reusable email templates with {{variable}} substitutions. Manage templates via the SDK or the Console.
Create a template
const tpl = await mail.templates.create({
name: "Welcome Email",
subject: "Welcome {{name}}!",
html_content: "<h1>Hi {{name}}</h1><p>Welcome to {{company}}.</p>",
variables: [
{ name: "name", type: "string", required: true },
{ name: "company", type: "string", default_value: "OonruMail" },
],
category: "onboarding",
tags: ["welcome", "new-user"],
});List & search
// List all templates in a category
const { templates } = await mail.templates.list({
category: "onboarding",
});
// Clone an existing template
const copy = await mail.templates.clone(tpl.id, {
name: "Welcome v2",
});Render preview
Preview a template with substitutions without sending an email:
const rendered = await mail.templates.render(tpl.id, {
name: "Alice",
});
console.log(rendered.html); // "<h1>Hi Alice</h1>..."
console.log(rendered.subject); // "Welcome Alice!"Update & delete
// Update
await mail.templates.update(tpl.id, {
subject: "Hey {{name}}!",
});
// Delete
await mail.templates.delete(tpl.id);Webhooks
Receive real-time notifications when email events occur. Configure endpoints in the Console or via the SDK.
Create an endpoint
The signing secret is only returned on creation — save it securely.
const wh = await mail.webhooks.create({
url: "https://myapp.com/hooks/email",
events: ["delivered", "bounced", "opened", "clicked"],
description: "Production webhook",
});
console.log(wh.secret); // Save this!Event types
OonruMail can send the following webhook events:
deliveredEmail accepted by recipient server
bouncedDelivery failed (hard or soft)
deferredTemporarily delayed, will retry
droppedNot sent (suppression or policy)
openedRecipient opened the email
clickedRecipient clicked a link
spam_reportMarked as spam
unsubscribedRecipient unsubscribed
processedEmail accepted for delivery
Verify webhook signatures
All webhook payloads are signed with HMAC-SHA256. Always verify the signature before processing:
import { OonruMail } from "@oonrumail/sdk";
// In your Express / Next.js / Fastify handler:
const isValid = await OonruMail.verifyWebhookSignature(
rawBody,
request.headers["x-webhook-signature"],
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
const payload = OonruMail.parseWebhookPayload(rawBody);
switch (payload.event) {
case "delivered":
console.log(`Delivered to ${payload.recipient}`);
break;
case "bounced":
console.log(`Bounced: ${payload.bounce_type} — ${payload.reason}`);
break;
}Test & rotate
Test your endpoint or rotate the signing secret at any time:
// Test endpoint
const test = await mail.webhooks.test(wh.id, "delivered");
console.log(test.success, test.latency_ms);
// Rotate signing secret
const { secret } = await mail.webhooks.rotateSecret(wh.id);
// View recent deliveries (filter for failures)
const { deliveries } = await mail.webhooks.listDeliveries(wh.id, {
limit: 10,
success: false,
});Suppressions
The suppression list prevents sending to addresses that have bounced, unsubscribed, or been manually added. Manage your list via the Console or the SDK.
Check an address
Check whether addresses are suppressed before sending:
const check = await mail.suppressions.check(["user@example.com"]);
if (check.results["user@example.com"].suppressed) {
console.log("Email is suppressed — skipping");
}Add & bulk add
// Add a single address
await mail.suppressions.create({
email: "bad@example.com",
reason: "manual",
description: "User requested removal",
});
// Bulk add (up to 1,000)
const result = await mail.suppressions.bulkCreate({
emails: ["a@bad.com", "b@bad.com"],
reason: "invalid",
});
console.log(`Added: ${result.added}`);
console.log(`Already existed: ${result.existing}`);Suppression stats
const stats = await mail.suppressions.stats();
console.log(`Total suppressed: ${stats.total}`);Messages & Events
List messages
Query your sent messages with status filtering and pagination:
const { messages } = await mail.messages.list({
status: "delivered",
limit: 20,
});Delivery timeline
Get the full delivery timeline for a specific message:
const timeline = await mail.messages.timeline(messageId);
for (const event of timeline.events) {
console.log(`${event.event_type} at ${event.timestamp}`);
}Raw events
Query raw email events across all messages:
const events = await mail.events.list({
event_type: "bounced",
limit: 50,
});Analytics
Access delivery stats, engagement metrics, and real-time data programmatically. Also available in the Console → Analytics.
Overview stats
const stats = await mail.analytics.overview({
start_date: "2026-01-01",
end_date: "2026-01-31",
});
console.log(`Delivery rate: ${stats.delivery_rate}%`);
console.log(`Open rate: ${stats.open_rate}%`);
console.log(`Total sent: ${stats.total_sent}`);Time series
Get time-bucketed stats for charts and dashboards:
const ts = await mail.analytics.timeseries({
interval: "day", // "hour" | "day" | "week" | "month"
});
for (const point of ts.data) {
console.log(`${point.date}: ${point.sent} sent, ${point.delivered} delivered`);
}Bounce analysis
const bounces = await mail.analytics.bounces();
console.log(`Hard bounces: ${bounces.hard}`);
console.log(`Soft bounces: ${bounces.soft}`);
console.log(`Top reasons:`, bounces.top_reasons);Real-time stats
const rt = await mail.analytics.realtime();
console.log(`${rt.sent_last_minute} emails/min`);
console.log(`${rt.active_connections} active connections`);Sender reputation
const rep = await mail.analytics.reputation();
console.log(`Reputation score: ${rep.score}`);
console.log(`Status: ${rep.status}`); // "good" | "warning" | "critical"API Keys
AdminManage API keys programmatically. Create keys with specific scopes and limits. Also available in the Console → API Keys.
Create a key
The raw key value is only returned on creation — store it securely.
const { key, api_key } = await mail.apiKeys.create({
domain_id: "domain-uuid",
name: "Platform X Production",
scopes: ["send", "read", "templates"],
rate_limit: 500, // requests per minute
daily_limit: 10000, // emails per day
});
console.log(`Key: ${key}`); // Only shown once!Available scopes
Each API key can be limited to specific operations:
| Scope | Allows |
|---|---|
send | Send emails and batch send |
read | Read messages, events, and activity |
templates | CRUD operations on templates |
webhooks | Manage webhook endpoints |
analytics | Access analytics and stats |
suppressions | Manage suppression lists |
admin | Full access (manage keys, domains) |
Rotate & revoke
// Check usage over last 7 days
const usage = await mail.apiKeys.usage(api_key.id, 7);
// Rotate (revoke old key, create new one)
const rotated = await mail.apiKeys.rotate(api_key.id);
console.log(`New key: ${rotated.key}`);
// Revoke permanently
await mail.apiKeys.revoke(api_key.id);Error Handling
The SDK throws typed errors with status codes, error codes, and retry information:
import {
OonruMail,
OonruMailError,
OonruMailTimeoutError,
} from "@oonrumail/sdk";
try {
await mail.send({ from: "bad", to: [], subject: "" });
} catch (e) {
if (e instanceof OonruMailError) {
console.log(e.status); // 400
console.log(e.code); // "validation_error"
console.log(e.message); // "Invalid email address"
console.log(e.retryable); // false (true for 429/5xx)
}
if (e instanceof OonruMailTimeoutError) {
console.log("Request timed out");
}
}The SDK automatically retries on 5xx server errors and 429 rate limit responses with exponential backoff. You can configure the number of retries via the maxRetries option.
| Status | Meaning | Retryable |
|---|---|---|
400 | Bad request — check your parameters | No |
401 | Invalid API key | No |
403 | Key lacks required scope | No |
404 | Resource not found | No |
429 | Rate limited — slow down | Yes |
500 | Server error | Yes |
503 | Service temporarily unavailable | Yes |
TypeScript Types
All types are exported for full type safety. Import what you need:
import type {
// Send
SendRequest,
SendResponse,
BatchSendRequest,
BatchSendResponse,
SimpleSendRequest,
TemplateSendRequest,
// Templates
Template,
CreateTemplateRequest,
TemplateVariable,
// Webhooks
Webhook,
WebhookPayload,
WebhookEventType,
// Suppressions
Suppression,
SuppressionReason,
// Analytics
OverviewStats,
TimeseriesPoint,
// API Keys
ApiKey,
ApiKeyScope,
// Config
OonruMailConfig,
} from "@oonrumail/sdk";The SDK is written in TypeScript and ships with declaration files. You get full autocomplete and type checking in your editor with no additional setup.