Matrix: M04
Open in panel — /app
Reference

SDKs & Libraries

PostMTA provides officially maintained client libraries for four languages. All SDKs are generated from the OpenAPI specification and expose the complete PostMTA REST API surface with idiomatic error handling, automatic pagination, and typed request/response objects.

Node.js / TypeScript SDK

Installation

npm install @postmta/node

Requires Node.js 18 or later. TypeScript is included as a peer dependency; no separate @types package is required.

Setup

import { PostMTA } from '@postmta/node';

const client = new PostMTA({
  apiKey: process.env.POSTMTA_API_KEY,
  workspaceId: 'ws_01HXABC123DEF456',
});

Send a Message

import { PostMTA } from '@postmta/node';

const client = new PostMTA({ apiKey: process.env.POSTMTA_API_KEY });

const result = await client.messages.send({
  from: 'hello@example.com',
  to: 'recipient@example.com',
  subject: 'Welcome to Acme Corp',
  html: '<html><body><h1>Welcome!</h1><p>...</p></body></html>',
  text: 'Welcome! ...',
  tags: ['welcome', 'onboarding'],
  customArgs: { userId: '12345' },
});

console.log(result.data.id); // msg_01HXABC123DEF456

List Messages

const result = await client.messages.list({
  limit: 50,
  campaignId: 'camp_01HXABC123DEF456',
});

for await (const message of result) {
  console.log(message.id, message.status);
}

The SDK supports async iteration for automatic cursor-based pagination. No need to manage cursors manually.

Manage Webhooks

const webhook = await client.webhooks.create({
  url: 'https://yourapp.com/webhooks/postmta',
  events: ['message.delivered', 'message.bounced', 'message.complained'],
  secret: 'whsec_your_signing_secret',
});

console.log(webhook.data.id); // whk_01HXABC123DEF456

Read Analytics

const summary = await client.analytics.getSummary({
  start: '2026-07-01',
  end: '2026-07-18',
});

console.log(summary.data.rates.deliverRate);   // 98.71
console.log(summary.data.rates.openRate);       // 60.80

Error Handling

import { PostMTA, RateLimitError, ValidationError } from '@postmta/node';

try {
  await client.messages.send({ ... });
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log('Rate limited. Retry after:', err.retryAfterSeconds);
  } else if (err instanceof ValidationError) {
    console.log('Validation failed:', err.details);
  } else {
    throw err; // unexpected error
  }
}

Python SDK

Installation

pip install postmta-python

Requires Python 3.9 or later. Supports both sync and async usage via httpx.

Setup

from postmta import PostMTA
import os

client = PostMTA(
    api_key=os.environ["POSTMTA_API_KEY"],
    workspace_id="ws_01HXABC123DEF456",
)

Async Usage

import asyncio
from postmta.async_client import AsyncPostMTA

async def main():
    client = AsyncPostMTA(api_key="pmta_l_...")

    result = await client.messages.send(
        from_="hello@example.com",
        to="recipient@example.com",
        subject="Hello",
        html="<html><body>Hello!</body></html>",
    )
    print(result.data.id)

asyncio.run(main())

Send a Message

result = client.messages.send(
    from_="hello@example.com",
    to="recipient@example.com",
    subject="Your Invoice",
    html="<html><body>...</body></html>",
    text="Plain text version",
    tags=["invoice", "july"],
    send_at="2026-07-20T09:00:00Z",
)

print(result.data.id)  # msg_01HXABC123DEF456
print(result.data.status)  # accepted

Analytics

summary = client.analytics.get_summary(
    start="2026-07-01",
    end="2026-07-18",
)

print(summary.data.rates.deliver_rate)  # 98.71
print(summary.data.rates.open_rate)    # 60.80

Error Handling

from postmta.exceptions import RateLimitError, ValidationError, APIError

try:
    client.messages.send(from_="bad", to="bad", subject="Test", html="...")
except ValidationError as e:
    print("Validation:", e.message, e.details)
except RateLimitError as e:
    print("Rate limited. Retry after", e.retry_after_seconds)
except APIError as e:
    print("API error:", e.status, e.message)

Go SDK

Installation

go get github.com/postmta/postmta-go

Setup

package main

import (
    "os"
    postmta "github.com/postmta/postmta-go"
)

func main() {
    client := postmta.New(postmta.Config{
        APIKey:      os.Getenv("POSTMTA_API_KEY"),
        WorkspaceID: "ws_01HXABC123DEF456",
    })
}

Send a Message

package main

import (
    "context"
    "fmt"
    "os"
    postmta "github.com/postmta/postmta-go"
)

func main() {
    client := postmta.New(postmta.Config{
        APIKey: os.Getenv("POSTMTA_API_KEY"),
    })

    resp, err := client.Messages.Send(context.Background(), postmta.SendMessageInput{
        From:    "hello@example.com",
        To:      "recipient@example.com",
        Subject: "Welcome",
        HTML:    "<html><body>Hello</body></html>",
        Tags:    []string{"welcome"},
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(resp.Data.ID)
}

Analytics

resp, err := client.Analytics.GetSummary(context.Background(), postmta.AnalyticsSummaryInput{
    Start: "2026-07-01",
    End:   "2026-07-18",
})
if err != nil {
    panic(err)
}
fmt.Printf("Deliver rate: %.2f%%\n", resp.Data.Rates.DeliverRate)
fmt.Printf("Open rate:    %.2f%%\n", resp.Data.Rates.OpenRate)

Pagination

iter := client.Messages.List(context.Background(), postmta.MessagesListInput{
    Limit: 50,
})

for iter.Next() {
    msg := iter.Current()
    fmt.Println(msg.ID, msg.Status)
}
if err := iter.Err(); err != nil {
    panic(err)
}

Error Handling

import (
    "errors"
    postmta "github.com/postmta/postmta-go"
)

_, err := client.Messages.Send(ctx, input)
if err != nil {
    var apiErr *postmta.APIError
    if errors.As(err, &apiErr) {
        fmt.Printf("Status: %d, Code: %s\n", apiErr.Status, apiErr.Code)
        if apiErr.RateLimit != nil {
            fmt.Printf("Retry after: %d seconds\n", apiErr.RateLimit.RetryAfterSeconds)
        }
    }
    panic(err)
}

Ruby Gem

Installation

gem install postmta

Or add to your Gemfile:

gem 'postmta', '~> 1.0'

Setup

require 'postmta'

client = PostMTA::Client.new(
  api_key: ENV['POSTMTA_API_KEY'],
  workspace_id: 'ws_01HXABC123DEF456'
)

Send a Message

result = client.messages.send(
  from: 'hello@example.com',
  to: 'recipient@example.com',
  subject: 'Welcome',
  html: '<html><body>Hello</body></html>',
  tags: ['welcome', 'onboarding']
)

puts result.data.id       # msg_01HXABC123DEF456
puts result.data.status   # accepted

Webhooks

webhook = client.webhooks.create(
  url: 'https://yourapp.com/webhooks/postmta',
  events: ['message.delivered', 'message.bounced'],
  secret: 'whsec_your_secret'
)

puts webhook.data.id  # whk_01HXABC123DEF456

Analytics

summary = client.analytics.summary(
  start: '2026-07-01',
  end: '2026-07-18'
)

puts summary.data.rates.deliver_rate  # 98.71
puts summary.data.rates.open_rate    # 60.80

Community Libraries

The following community-maintained libraries are not officially supported but are known to be actively maintained:

LanguagePackageRepositoryNotes
PHPpostmta/postmta-phpgithub.com/community/postmta-phpPHP 8.1+; not officially supported
Javacom.postmta:postmta-javagithub.com/community/postmta-javaJava 17+; not officially supported
.NET / C#PostMTA.PostMTAgithub.com/community/postmta-dotnet.NET 8+; not officially supported
Community libraries are not covered by PostMTA's official support SLA. Use them at your own discretion. If you encounter a bug, open an issue on the respective community repository.

HTTP API Directly (No SDK)

Overview

All PostMTA SDKs are thin wrappers around the REST API. You can call the API directly with any HTTP client without using an SDK. See the API Reference for the full endpoint documentation.

Minimal cURL Example

curl -X POST https://api.postmta.com/v1/messages \
  -H "Authorization: Bearer pmta_l_01HXABC123DEF456GHI789JKL012MNO345PQR" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "hello@example.com",
    "to": "recipient@example.com",
    "subject": "Test",
    "html": "<html><body>Hello</body></html>"
  }'

Verifying Webhook Signatures

When your endpoint receives a webhook POST from PostMTA, verify the signature using the secret you provided at registration:

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

// In your Express handler:
app.post('/webhooks/postmta', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-postmta-signature'];
  if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // process event...
  res.status(200).send('OK');
});

Idempotency

For POST requests that create or modify state, pass an Idempotency-Key header to prevent duplicate operations if a request is retried:

curl -X POST https://api.postmta.com/v1/messages \
  -H "Authorization: Bearer pmta_l...xxxx" \
  -H "Idempotency-Key: msg-001-abc123" \
  -H "Content-Type: application/json" \
  -d '{ ... }'

PostMTA stores idempotency keys for 24 hours. If the same key is seen twice within that window, the original response is returned without re-executing the operation.