@oonrumail/sdk v1.0

Developer Documentation

Everything you need to integrate OonruMail into your application. TypeScript SDK with full type safety, zero dependencies, and automatic retries.

Node.js 18+
Bun & Deno
Cloudflare Workers
Zero dependencies

Quick Start

Start here

Installation

Install the SDK via your preferred package manager:

npm install @oonrumail/sdk
pnpm add @oonrumail/sdk
yarn add @oonrumail/sdk

Configuration

Create a client instance with your API key. You can get an API key from the Console → API Keys.

config.ts
ts
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:

send.ts
ts
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:

full-send.ts
ts
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:

delivered

Email accepted by recipient server

bounced

Delivery failed (hard or soft)

deferred

Temporarily delayed, will retry

dropped

Not sent (suppression or policy)

opened

Recipient opened the email

clicked

Recipient clicked a link

spam_report

Marked as spam

unsubscribed

Recipient unsubscribed

processed

Email accepted for delivery

Verify webhook signatures

All webhook payloads are signed with HMAC-SHA256. Always verify the signature before processing:

webhook-handler.ts
ts
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

Admin

Manage 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:

ScopeAllows
sendSend emails and batch send
readRead messages, events, and activity
templatesCRUD operations on templates
webhooksManage webhook endpoints
analyticsAccess analytics and stats
suppressionsManage suppression lists
adminFull 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:

error-handling.ts
ts
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.

StatusMeaningRetryable
400Bad request — check your parametersNo
401Invalid API keyNo
403Key lacks required scopeNo
404Resource not foundNo
429Rate limited — slow downYes
500Server errorYes
503Service temporarily unavailableYes

TypeScript Types

All types are exported for full type safety. Import what you need:

types.ts
ts
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.