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/nodeRequires 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_01HXABC123DEF456List 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_01HXABC123DEF456Read 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.80Error 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-pythonRequires 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) # acceptedAnalytics
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.80Error 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-goSetup
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 postmtaOr 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 # acceptedWebhooks
webhook = client.webhooks.create(
url: 'https://yourapp.com/webhooks/postmta',
events: ['message.delivered', 'message.bounced'],
secret: 'whsec_your_secret'
)
puts webhook.data.id # whk_01HXABC123DEF456Analytics
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.80Community Libraries
The following community-maintained libraries are not officially supported but are known to be actively maintained:
| Language | Package | Repository | Notes |
|---|---|---|---|
| PHP | postmta/postmta-php | github.com/community/postmta-php | PHP 8.1+; not officially supported |
| Java | com.postmta:postmta-java | github.com/community/postmta-java | Java 17+; not officially supported |
| .NET / C# | PostMTA.PostMTA | github.com/community/postmta-dotnet | .NET 8+; not officially supported |
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.