Skip to content

SDKs

Last updated 7 September 2026

Two zero-dependency SDKs designed for high throughput and autonomous agent integration. Built on native runtime capabilities — no CVE supply-chain overhead.

Zero dependencies by design

Hand-written, not generated. Typed template variables, a distinct error class per failure mode, and no required configuration beyond the key.

Every dependency in an email client is an attack vector, a bundle bloater, and an eventual CVE notice. SadaSend SDKs declare zero external runtime dependencies.

TypeScript uses native global fetch and WebCrypto across Node 18+, Bun, Deno, and Edge runtimes. Python uses standard library urllib.request and hmac.

The autonomous agent moat: server-enforced ceilings

If an agent could pass its own scopes or allowlists in code (new SadaSend({ allowlist: ... })), a prompt injection or hallucinated loop could simply rewrite the client configuration.

In SadaSend, the ceiling is set once when the key is minted in the dashboard or via human session. The agent receives only the key (sada_agent_sk_…) and has zero ability to widen its permissions, increase rate limits, or bypass approval mode.

  • Recipient Allowlists: Out-of-bounds sends are rejected immediately before reaching the mail provider.
  • Approval Mode: Outbound sends are placed into pending_approval for human sign-off in the dashboard.
  • Key Expiration & CIDR IP Guardrails: Hard ISO expiration dates (expires_at) and network IP restrictions (ip_allowlist) ensure credentials in agent runtimes are inert outside designated VPCs or past scheduled job windows.
  • Hard Rate Limits: Throttles runaway agent loops with HTTP 429.
  • Preflight Dry Runs: dryRun: true lets agents validate templates, schemas, and deliverability with zero cost.

TypeScript SDK

Type-safe discriminated unions (Result<T, E>), automatic retry with full jitter, built-in idempotency, and type narrowing.

TYPESCRIPT
// bun add sadasend (or npm i sadasend)
import { SadaSend, RecipientNotAllowlistedError } from 'sadasend';

const sadasend = new SadaSend(process.env.SADASEND_API_KEY!);

// 1. Transactional send with cc, bcc, attachments, tags, provenance & idempotency
const res = await sadasend.emails.send({
  from: 'billing@yourdomain.com',
  to: 'customer@example.com',
  cc: ['finance@example.com'],
  bcc: ['audit@yourdomain.com'],
  reply_to: 'support@yourdomain.com',
  subject: 'Monthly Statement',
  html: '<h1>Your Statement</h1><p>Please find attached.</p>',
  tags: ['billing', 'monthly'],
  headers: {
    'X-SadaSend-Agent': 'Invoice Automation',
    'X-SadaSend-Source': 'automation',
  },
  attachments: [{
    filename: 'statement.pdf',
    content: Buffer.from('%PDF-1.4...').toString('base64'),
    contentType: 'application/pdf',
  }],
  idempotencyKey: 'stmt-2026-09-user-123',
});

// 2. Refusals are results, not exceptions
if (!res.ok) {
  if (res.error instanceof RecipientNotAllowlistedError) {
    console.error('Allowlist violation. Allowed:', res.error.allowlist);
  }
} else {
  // res.status is 'queued' for live keys, or 'pending_approval' for agent keys
  console.log(res.status, res.id);
}

// 3. Batch sends (up to 100 per call, RFC 4918 Multi-Status)
const batch = await sadasend.emails.batch([
  { from: 'updates@yourdomain.com', to: 'alice@example.com', text: 'Hi Alice' },
  { from: 'updates@yourdomain.com', to: 'bob@example.com', text: 'Hi Bob' },
]);

Python SDK

Generated from the published OpenAPI specification, so it stays in step with the API automatically.

Clean standard-library client matching 100% of the public API surface with typed exceptions.

PYTHON
# pip install sadasend
import os, base64
from sadasend import SadaSend, RecipientNotAllowlistedError

sadasend = SadaSend(os.environ["SADASEND_API_KEY"])

try:
    # 1. Transactional send with cc, bcc, attachments, tags, and provenance
    pdf_b64 = base64.b64encode(b"%PDF-1.4...").decode("utf-8")
    result = sadasend.emails.send(
        from_="billing@yourdomain.com",
        to="customer@example.com",
        cc=["finance@example.com"],
        bcc=["audit@yourdomain.com"],
        reply_to="support@yourdomain.com",
        subject="Monthly Statement",
        html="<h1>Your Statement</h1>",
        tags=["billing", "monthly"],
        headers={
            "X-SadaSend-Agent": "Billing Worker",
            "X-SadaSend-Source": "backend",
        },
        attachments=[{
            "filename": "statement.pdf",
            "content": pdf_b64,
            "contentType": "application/pdf",
        }],
        idempotency_key="stmt-2026-09-user-123",
    )
    print(result["status"], result["id"])

except RecipientNotAllowlistedError as e:
    # Typed exception with remediation context attached
    print(f"Refused: {e.recipient} not in allowlist {e.allowlist}")

# 2. Preflight dry run (zero cost validation)
dry_run = sadasend.emails.send(
    from_="billing@yourdomain.com",
    to="customer@example.com",
    subject="Validation",
    text="Test",
    dry_run=True,
)

What both ship with

  • Automatic retry with exponential backoff on 5xx and 429, respecting Retry-After.
  • Idempotency keys generated by default, so a retried send is never a duplicate email.
  • A webhook signature verifier — everyone needs one, and getting it wrong is a security hole.
  • Errors that name the fix. DomainNotVerifiedError carries the missing records.

Complete resource reference

ResourceMethodScopeDescription
emailssend(...)sendSingle send with attachments, tags, dry-run or approval mode
emailsbatch(...)sendHigh-throughput atomic batch send up to 100 recipients
emailsget(id)readDelivery status and event trail without leaking message body
emailsresend(id)sendResend an email under its original issuing key
emailscancel(id)sendCancel a scheduled or pending email before dispatch
templateslist()readList versioned Liquid templates on this account
templatesget(id)readFetch stored template body, schema, and version history
templatesrender(...)readRender a template against variable values without sending
templatestest(id, ...)sendDispatch test send with custom JSON variables to verified recipient
domainslist()readList registered sending domains and statuses
domainsrecords(id)readFetch required SPF, DKIM, and DMARC DNS records
domainsverify(id)domainsTrigger live DNS query to verify records on internet
domainsrestore(id)domainsRestore a previously removed domain without regenerating DKIM DNS keys
webhookslist()readList registered webhook delivery endpoints and statuses
webhookscreate(...)webhooksRegister signed webhook endpoint with event subscriptions
webhookssimulate(...)readSimulate webhook payload delivery to a local or remote URL with HMAC signing
suppressionscheck(addr)readCheck if address is suppressed (bounce, complaint, unsubscribe)
suppressionsadd(...)sendAdd an address manually to account suppression list
accountget()readRetrieve account plan, email quota, and sending utilization
accountvolume(...)readQuery daily sending volume array for sparkline charts over a date window
accountstats(...)readQuery bounce rates, complaint rates, and sparkline volume. Pass provenance=true for byChannel, bySource, and topAgents breakdown matrices

Constant-time HMAC webhook verification

Timing-safe, replay-protected webhook parsing is built directly into both SDKs (parseWebhook in TypeScript, parse_webhook in Python).

Signatures are compared in constant time with a 5-minute replay tolerance window.

TYPESCRIPT
import { parseWebhook } from 'sadasend/webhooks';

// In Next.js / Remix / Express / Fastify / Cloudflare:
const raw = await req.text(); // Raw body string, NOT parsed JSON!
const event = await parseWebhook(process.env.WEBHOOK_SECRET!, raw, req.headers.get('sadasend-signature'));

if (!event) return new Response('Bad signature', { status: 400 });
console.log(event.type, event.data.messageId);

SMTP Relay

If you already have a working mailer, you do not need an SDK at all. Point it at the relay and change credentials rather than code.

If you already use an existing mail framework (Nodemailer, Django, Laravel, Postfix), point your SMTP transport to SadaSend with zero code rewrites.

TEXT
host:     smtp.sadasend.com
port:     587
security: STARTTLS
user:     sadasend
pass:     $SADASEND_API_KEY