@poststack.dev/sdk
v0.12.0
Published
Official TypeScript SDK for the PostStack Email API
Maintainers
Readme
@poststack.dev/sdk
Official TypeScript SDK for the PostStack Email API — a GDPR-compliant, EU-hosted alternative to Resend, SendGrid, and Postmark with built-in mailboxes, broadcasts, and real-time analytics.
Full API reference: poststack.dev/docs/sdk.
Installation
npm install @poststack.dev/sdk
# or
bun add @poststack.dev/sdkQuick Start
import { PostStack } from '@poststack.dev/sdk';
const client = new PostStack('sk_live_...');
// Send an email
const { id } = await client.emails.send({
from: '[email protected]',
to: ['[email protected]'],
subject: 'Hello!',
html: '<p>Welcome!</p>',
});Resources
client.emails
Send, batch, list, get, reschedule, and cancel emails. Retrieve per-email events and deliverability insights.
// Send a single email
await client.emails.send({ from: '...', to: ['...'], subject: '...', html: '...' });
// Batch send
await client.emails.batch({ emails: [...] });
// Get email events
const { events } = await client.emails.getEvents(emailId);
// Get deliverability insights / warnings
const { warnings } = await client.emails.getInsights(emailId);client.domains
Manage sending domains, verify DNS records, assign dedicated IPs, and access DMARC reports.
// Create and verify a domain
const domain = await client.domains.create({ name: 'mail.example.com' });
await client.domains.verify(domain.id);
// Assign a dedicated IP
await client.domains.assignIp(domain.id, ipAddressId);
// DMARC reports
const reports = await client.domains.getDmarcReports(domain.id);
const stats = await client.domains.getDmarcStats(domain.id, 30); // last 30 days
const sources = await client.domains.getDmarcSources(domain.id);client.contacts
Create, update, list, import, export, and manage contacts.
// Create a contact
await client.contacts.create({ email: '[email protected]', first_name: 'Jane' });
// Bulk import contacts
const result = await client.contacts.import({
contacts: [{ email: '[email protected]' }, { email: '[email protected]' }],
update_existing: true,
});
// Export all contacts as CSV
const csv = await client.contacts.exportCsv();client.contactProperties
Define custom properties for contacts.
// List all custom properties
const { properties } = await client.contactProperties.list();
// Create a new property
await client.contactProperties.create({
name: 'plan',
label: 'Plan',
type: 'select',
options: ['free', 'pro', 'enterprise'],
});
// Update or delete
await client.contactProperties.update(id, { label: 'Subscription Plan' });
await client.contactProperties.delete(id);client.segments
Create and manage contact segments, including rule-based preview.
// Preview how many contacts match a rule set before saving
const preview = await client.segments.previewRules({
rules: [{ field: 'plan', operator: 'eq', value: 'pro' }],
logic: 'and',
});
// Add / remove contacts
await client.segments.addContacts(segmentId, { contact_ids: ['...'] });
await client.segments.removeContact(segmentId, contactId);client.subscriptionTopics
Manage opt-in topics and contact subscriptions.
// Create a topic
const { topic } = await client.subscriptionTopics.create({ name: 'Product updates' });
// Subscribe / unsubscribe a contact (IDs are publicIds: con_… and top_…)
await client.subscriptionTopics.subscribe(contact.id, topic.publicId);
await client.subscriptionTopics.unsubscribe(contact.id, topic.publicId);
// Get all subscriptions for a contact
const { subscriptions } = await client.subscriptionTopics.getContactSubscriptions(contact.id);client.templates
Email templates with versioning, presets, publish/unpublish, and duplicate.
// Use a built-in preset as a starting point
const presets = await client.templates.getPresets();
const template = await client.templates.usePreset(presets[0].id);
// Duplicate an existing template
const copy = await client.templates.duplicate(templateId);
// Publish for use in broadcasts / API sends
await client.templates.publish(templateId);client.broadcasts
Marketing email broadcasts with A/B variant tracking.
// Create and send a broadcast
const { broadcast } = await client.broadcasts.create({
segment_id: segmentId,
from: '[email protected]',
subject: 'Monthly update',
html: '<p>...</p>',
});
await client.broadcasts.send(broadcast.publicId);
// A/B variant results
const { variants } = await client.broadcasts.getVariants(broadcast.publicId);
const stats = await client.broadcasts.getVariantStats(broadcast.publicId);client.webhooks
Webhook endpoint management including delivery history and replay.
// List delivery history
const deliveries = await client.webhooks.getDeliveries(webhookId);
// Replay a failed delivery
await client.webhooks.replay(webhookId, deliveryId);Verifying webhook signatures
Every webhook we send carries an X-PostStack-Signature header. Verify it
before trusting the payload — without this check your endpoint will accept
anything anyone posts to it.
Verification needs no client and no API key, only the raw body, the header and the endpoint's signing secret, so it is a static:
import { PostStack } from '@poststack.dev/sdk';
// Express — note `express.raw`, not `express.json`.
app.post('/webhooks/poststack', express.raw({ type: 'application/json' }), async (req, res) => {
const ok = await PostStack.Webhooks.verify(
req.body.toString('utf8'),
req.header('X-PostStack-Signature') ?? '',
process.env.POSTSTACK_WEBHOOK_SECRET!,
);
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString('utf8'));
// …handle the event
res.status(200).end();
});Pass the raw body exactly as received. Parsing to an object and re-serialising changes the bytes and the HMAC will not match.
If your handler never calls the API, import the resource on its own so the client is not bundled with it:
import { WebhooksResource } from '@poststack.dev/sdk';
const ok = await WebhooksResource.verify(rawBody, signatureHeader, secret);The header is a comma-separated list of sha256=<hex> signatures — today
always exactly one. Verification passes if your secret matches any entry,
so write your handler against the list form and a future signing-secret
rotation will need no change on your side.
client.suppressions
Manage the suppression / unsubscribe list.
await client.suppressions.add({ email: '[email protected]', reason: 'hard_bounce' });
await client.suppressions.remove('[email protected]');client.workflows
Automated email workflows triggered by contact events or manually.
// Create a workflow (returns the Workflow directly)
const workflow = await client.workflows.create({
name: 'Welcome series',
trigger_type: 'contact.created',
});
// Define the automation as a graph of nodes + edges (the v2 model — the old
// step API was removed). Replace the whole graph atomically:
await client.workflows.putGraph(workflow.publicId, {
nodes: [
{ public_id: 'n1', type: 'trigger', config: {}, canvas_x: 0, canvas_y: 0 },
{
public_id: 'n2',
type: 'send_email',
config: { template_id: '...' },
canvas_x: 0,
canvas_y: 120,
},
],
edges: [{ from_public_id: 'n1', to_public_id: 'n2', branch: null }],
});
// Optional: validate the stored graph before activating
const result = await client.workflows.validateGraph(workflow.publicId);
if (!result.valid) throw new Error(result.errors.join(', '));
// Activate
await client.workflows.activate(workflow.publicId);
// Manually trigger for a specific contact
await client.workflows.trigger(workflow.publicId, { contact_id: 'con_...' });client.signupForms
Embeddable signup forms that add contacts to segments and subscribe to topics.
// Create a form
const form = await client.signupForms.create({
name: 'Newsletter signup',
segment_id: segmentId,
topic_id: topicId,
success_message: 'Thanks for subscribing!',
});
// Submit (public, no auth required — call from your frontend)
await client.signupForms.submit(form.publicId, { email: '[email protected]' });client.emailValidations
Validate email addresses before sending to reduce bounces.
// Single validation
const result = await client.emailValidations.validate('[email protected]');
console.log(result.result); // 'deliverable' | 'undeliverable' | 'risky' | 'unknown'
// Batch validation
const { results } = await client.emailValidations.validateBatch(['[email protected]', '[email protected]']);client.mailboxes
Managed IMAP/POP3 mailboxes and aliases.
// Create a mailbox
const mailbox = await client.mailboxes.create({
domainId: domain.id,
localPart: 'support',
password: 'securepassword',
});
// Create an alias (destination is the mailbox publicId)
await client.mailboxes.createAlias({
domainId: domain.id,
localPart: 'help',
destinationMailboxId: mailbox.publicId,
});client.inboundEmails
Read, reply to, and forward inbound mail received on your verified domains. Inbound email IDs are numeric.
// List received messages (most recent first)
const { data, meta } = await client.inboundEmails.list({ page: 1, per_page: 20 });
// Fetch one with full body + headers
const email = await client.inboundEmails.get(data[0].id);
console.log(email.fromName, email.fromAddress, email.subject);
// Attachments
const attachments = await client.inboundEmails.listAttachments(email.id);
const bytes = await client.inboundEmails.downloadAttachment(email.id, attachments[0].id);
// Reply (threaded) or forward
await client.inboundEmails.reply(email.id, {
from: 'Support <[email protected]>',
text: 'Thanks for reaching out!',
});
await client.inboundEmails.forward(email.id, {
from: 'Support <[email protected]>',
to: ['[email protected]'],
});client.apiKeys
Create and manage API keys (requires session auth).
const key = await client.apiKeys.create({ name: 'Production', permission: 'sending_access' });
await client.apiKeys.revoke(key.id);Links
- PostStack email API — EU-hosted transactional + marketing email
- API documentation — REST reference + guides
- Pricing — free tier up to 3,000/month, paid from €5
- Compare providers — PostStack vs Resend, SendGrid, Postmark
- Migrate from Resend
- MCP server for AI agents
- Status · Security · DPA
