CtrlK
BlogDocsLog inGet started
Tessl Logo

postmark-send-email

Use when sending transactional or broadcast emails through Postmark — single sends, batch (up to 500), bulk, or template-based emails with support for attachments, tracking, and message streams.

91

2.15x
Quality

86%

Does it follow best practices?

Impact

99%

2.15x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No known issues

SKILL.md
Quality
Evals
Security

Send Email with Postmark

Overview

Postmark provides multiple endpoints for sending emails:

ApproachEndpointUse CaseLimits
SinglePOST /emailIndividual transactional emails1 email, 10 MB payload including attachments
BatchPOST /email/batchUp to 500 emails in one request500 emails, 50 MB payload including attachments
TemplatePOST /email/withTemplateDynamic content with server-side templates1 email, 10 MB payload including attachments
Batch TemplatePOST /email/batchWithTemplatesBulk templated emails500 emails, 50 MB payload including attachments
BulkPOST /email/bulkBroadcast stream campaignsNo fixed recipient cap, 50 MB payload including attachments

Choose batch when: sending 2+ distinct emails at once, performance matters, or attachments are needed. Choose single when: sending one email or real-time per-message error handling is needed.

Message Streams (CRITICAL)

Postmark separates emails by intent. Always specify MessageStream:

StreamValuePurposeSMTP Endpoint
Transactionaloutbound1:1 triggered emails (default)smtp.postmarkapp.com
BroadcastbroadcastMarketing, newsletterssmtp-broadcasts.postmarkapp.com

Never mix transactional and broadcast in the same stream — it damages deliverability. Servers can have up to 10 message streams.

Quick Start

  1. Get API Token from your Postmark server settings
  2. Verify sender domain or email address
  3. Install SDK — see references/installation.md
  4. Choose endpoint based on the decision matrix above

Authentication

All API requests require the Server API Token:

X-Postmark-Server-Token: your-server-token-here

Store the token in POSTMARK_SERVER_TOKEN. For testing without sending, use POSTMARK_API_TEST as the token value.

Single Email

Endpoint: POST https://api.postmarkapp.com/email

Required Parameters

ParameterTypeDescription
FromstringSender address (must be a verified domain or sender signature)
TostringRecipients (comma-separated, max 50 total with Cc/Bcc)
SubjectstringEmail subject line
TextBody or HtmlBodystringMessage content (at least one required)

Optional Parameters

ParameterTypeDescription
CcstringCC recipients
BccstringBCC recipients
ReplyTostringReply-to address
MessageStreamstringoutbound (default) or broadcast
TagstringCategory for statistics (one per message, max 1000 chars)
MetadataobjectKey-value pairs for custom tracking data (returned in webhook payloads)
TrackOpensbooleanEnable open tracking
TrackLinksstringNone, HtmlAndText, HtmlOnly, TextOnly
HeadersarrayCustom email headers [{Name, Value}]
AttachmentsarrayFile attachments (max 10 MB total)
const postmark = require('postmark');
const client = new postmark.ServerClient(process.env.POSTMARK_SERVER_TOKEN);

const result = await client.sendEmail({
  From: 'notifications@yourdomain.com',
  To: 'customer@example.com',
  Subject: 'Your order has shipped',
  TextBody: 'Your order #12345 is on its way!',
  HtmlBody: '<p>Your order <strong>#12345</strong> is on its way!</p>',
  MessageStream: 'outbound',
  Tag: 'order-shipped'
});

console.log('MessageID:', result.MessageID);

Response includes MessageID — use it for tracking via webhooks or the Messages API.

See references/single-email-examples.md for Python, Ruby, PHP, .NET, and cURL examples.

Batch Email

Endpoint: POST https://api.postmarkapp.com/email/batch

Send up to 500 emails in a single API call. Each message is independently validated and can have its own attachments, tags, and metadata.

const results = await client.sendEmailBatch([
  {
    From: 'sender@yourdomain.com',
    To: 'user1@example.com',
    Subject: 'Order Shipped',
    TextBody: 'Your order has shipped!',
    MessageStream: 'outbound'
  },
  {
    From: 'sender@yourdomain.com',
    To: 'user2@example.com',
    Subject: 'Order Confirmed',
    TextBody: 'Your order is confirmed!',
    MessageStream: 'outbound'
  }
]);

// Check individual results — always handle partial failures
results.forEach((result, index) => {
  if (result.ErrorCode === 0) {
    console.log(`Email ${index + 1} sent: ${result.MessageID}`);
  } else {
    console.error(`Email ${index + 1} failed: ${result.Message}`);
  }
});

For more than 500 emails, chunk the array into groups of 500 and send sequentially. See references/batch-email-examples.md for chunking patterns, attachments, and Python/Ruby/cURL examples.

Send with Template

Endpoint: POST https://api.postmarkapp.com/email/withTemplate

Use server-side Handlebars templates — no client-side rendering needed. Always use TemplateAlias over TemplateId — aliases survive re-creation and work across environments.

const result = await client.sendEmailWithTemplate({
  From: 'sender@yourdomain.com',
  To: 'customer@example.com',
  TemplateAlias: 'order-confirmation',
  TemplateModel: {
    customer_name: 'Jane Doe',
    order_number: 'ORD-67890',
    items: [
      { name: 'Widget', price: '$19.99' },
      { name: 'Gadget', price: '$29.99' }
    ]
  },
  MessageStream: 'outbound'
});

For batch template sends (up to 500), use POST /email/batchWithTemplates via client.sendEmailBatchWithTemplates([...]). See references/template-examples.md for full examples.

Attachments

Include attachments as Base64-encoded content:

{
  "Attachments": [
    {
      "Name": "invoice.pdf",
      "Content": "base64-encoded-content-here",
      "ContentType": "application/pdf"
    }
  ]
}

Embed inline images using ContentID:

{
  "HtmlBody": "<img src=\"cid:logo123\">",
  "Attachments": [
    {
      "Name": "logo.png",
      "Content": "base64-encoded-image",
      "ContentType": "image/png",
      "ContentID": "cid:logo123"
    }
  ]
}

Size limits: TextBody/HtmlBody 5 MB each · Single message with attachments 10 MB · Batch payload 50 MB · Base64 encoding adds ~33% · Certain file types blocked (.exe, .bat)

Tracking

Configure per-email or at server level:

{ "TrackOpens": true, "TrackLinks": "HtmlAndText" }

TrackLinks options: None | HtmlAndText | HtmlOnly | TextOnly

Disable tracking for sensitive transactional emails (password resets, security alerts) to maximize deliverability.

Testing

MethodAddress/TokenResult
API Test TokenPOSTMARK_API_TESTValidates request without sending
Black Holetest@blackhole.postmarkapp.comDropped but appears in activity
Sandbox ServerCreate sandbox server in dashboardFull processing, no delivery
Hard Bouncehardbounce@bounce-testing.postmarkapp.comSimulates hard bounce
Soft Bouncesoftbounce@bounce-testing.postmarkapp.comSimulates soft bounce

Never test with fake addresses at real providers (e.g., test@gmail.com) — damages sender reputation.

Error Handling

CodeMeaningAction
200SuccessContinue
401UnauthorizedCheck API token — do not retry
406Inactive recipientCheck suppression list — do not retry
409JSON requiredFix Accept/Content-Type headers
410Too many batch messagesReduce to 500 or fewer per batch
413Payload too largeReduce payload (10 MB single, 50 MB batch)
422Validation errorFix request parameters — do not retry
429Rate limitedRetry with exponential backoff
500Server errorRetry with exponential backoff
async function sendWithRetry(client, email, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await client.sendEmail(email);
    } catch (error) {
      const isRetryable = error.statusCode === 429 || error.statusCode === 500;
      if (!isRetryable || attempt === maxRetries) throw error;
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}

See references/error-handling.md for complete error patterns including batch partial failures and typed error classes.

SMTP

Postmark supports SMTP for legacy integrations — host smtp.postmarkapp.com (transactional) or smtp-broadcasts.postmarkapp.com (broadcast), ports 25/2525/587, Server API Token as username and password. See references/smtp-migration.md for migration examples and custom header reference.

Common Mistakes

MistakeFix
Missing MessageStreamAlways specify outbound or broadcast
Using broadcast for transactionalUse separate streams for different email types
Testing with real addressesUse POSTMARK_API_TEST or sandbox mode
Retrying 422 errorsThese are validation errors — fix request, don't retry
Not handling partial batch failuresCheck each result in batch response array
Tracking on sensitive transactional emailsDisable for password resets, security alerts, receipts
Exceeding 50 recipients per emailSplit into multiple emails or use batch
Not verifying senderDomain or address must be verified before sending

Notes

  • From address must use a verified domain or sender signature
  • Store API key in POSTMARK_SERVER_TOKEN environment variable
  • Maximum 50 recipients total per email (To + Cc + Bcc)
  • MessageID returned in response is used for bounce/webhook/API correlation
  • For broadcast campaigns to large lists, use the Bulk API endpoint (POST /email/bulk)
  • Use POSTMARK_API_TEST as the token value in development and CI — validates without sending
  • New domains require gradual volume warm-up — see postmark-email-best-practices for the schedule
Repository
ActiveCampaign/postmark-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.