navidocs/INTEGRATIONS-SIP-POSTMARK.md

67 KiB
Raw Export PDF Blame History

Postmark Transactional Email API Integration Guide

InfraFabric Systems Integration Platform (SIP)

Research Date: 2025-11-14 Status: Complete Analysis (8-Pass IF.search Methodology) Integration Complexity: 5/10 Recommended For: High-reliability transactional email systems


Executive Summary

Postmark is a specialized transactional email service provider (TESP) designed specifically for reliable, fast delivery of triggered, one-to-one emails. Unlike general-purpose email platforms, Postmark focuses exclusively on transactional communications (password resets, invoices, alerts, confirmations) with separate infrastructure from broadcast/marketing email.

Key Differentiators:

  • Average delivery time: < 1 second (industry-leading)
  • Dedicated transactional infrastructure
  • Clear separation of message streams (transactional vs broadcast)
  • Transparent deliverability metrics (published every 5 minutes)
  • Simple, predictable pricing based on volume
  • 45-day message retention with searchable logs
  • GDPR-compliant with DPA and Standard Contractual Clauses
  • Enterprise-grade authentication (SPF, DKIM, DMARC support)

PASS 1: Signal Capture - API Surface Overview

1.1 Core API Endpoints

Postmark provides six primary API surface areas:

Email API

  • Single Email Endpoint: POST https://api.postmarkapp.com/email
  • Batch Email Endpoint: POST https://api.postmarkapp.com/email/batch
  • Authentication: X-Postmark-Server-Token header
  • Batch Capacity: Up to 500 messages per request, 50 MB payload
  • Response: JSON with MessageID, SubmittedAt, recipient information
{
  "From": "sender@example.com",
  "To": "recipient@example.com",
  "Subject": "Welcome to Our Service",
  "HtmlBody": "<html><body>Welcome!</body></html>",
  "TextBody": "Welcome!",
  "TrackOpens": true,
  "TrackLinks": "HtmlAndText",
  "Tag": "welcome-email"
}

Templates API

  • Endpoints:
    • GET/POST/PUT/DELETE /templates - Template CRUD operations
    • POST /email/withTemplate/ - Send single email with template
    • POST /email/batchWithTemplates - Batch send with templates
    • POST /templates/validate - Validate template syntax
  • Template Engine: Handlebars/Mustachio (supports variables, iterators)
  • Limit: Up to 100 templates per server (contact support for exceptions)
  • Layout System: Reusable layout templates for consistent styling

Message Streams API

  • Endpoints:
    • GET /message-streams - List all streams
    • GET /message-streams/{stream_ID} - Get stream details
    • POST /message-streams - Create new stream
    • PATCH /message-streams/{stream_ID} - Update stream configuration
    • POST /message-streams/{stream_ID}/archive - Archive stream
  • Stream Types: Transactional, Broadcast, Inbound
  • Per-Server Limit: Up to 10 streams (including defaults)
  • Configuration: Subscription settings, unsubscribe handling

Webhooks API

  • Event Types:
    • Bounce webhook (extended retry schedule: 1 min to 6 hours)
    • Delivery webhook (short retry: 1, 5, 15 minutes)
    • Open tracking webhook
    • Click tracking webhook
    • Spam complaint webhook
    • Inbound webhook (email routing)
    • Subscription change webhook
  • Configuration: Up to 10 webhooks per message stream
  • Security: HTTPS required, optional basic auth, IP whitelisting available
  • Retry Policy: Automatic retries with exponential backoff

Sender Signatures API

  • Endpoints:
    • GET /senders - List all sender signatures
    • GET /senders/{signatureid} - Get signature details
    • POST /senders - Create new signature
    • PUT /senders/{signatureid} - Update signature
    • DELETE /senders/{signatureid} - Remove signature
    • POST /senders/{signatureid}/resend - Resend verification email
  • Authentication: X-Postmark-Account-Token (account-level)
  • Features: DKIM configuration, Return-Path domain, reply-to settings

Bounce/Suppressions API

  • Query Endpoints:
    • GET /bounces - List bounces with filtering
    • GET /bounces/{bounceid} - Get bounce details
    • GET /bounce/tags - List bounce tags
    • GET /bounces/activation - Bounce reactivation status
  • Bounce Types:
    • Hard Bounce (permanent delivery failure)
    • Soft Bounce (temporary delivery failure)
    • Spam Complaint (user reported as spam)
    • Transient Bounce (temporary issues)
  • Suppression List: Automatically maintained indefinitely

1.2 Authentication Mechanisms

Server-Level Authentication:

X-Postmark-Server-Token: your-server-api-token
Content-Type: application/json
Accept: application/json

Account-Level Authentication:

X-Postmark-Account-Token: your-account-api-token

SMTP Authentication:

  • Username: Server API Token or SMTP Access Key
  • Password: Server API Token or SMTP Secret Key
  • Server: smtp.postmarkapp.com
  • Ports: 25, 587, 2525
  • TLS: Available via STARTTLS

1.3 Rate Limiting & Constraints

  • Message Limits:

    • Max recipients per message: 50 (To + Cc + Bcc combined)
    • Max attachments per message: 10 MB total
    • Max subject length: 2,000 characters
    • Max HTML body: 5 MB
    • Batch size: 500 messages, 50 MB total payload
  • Concurrent Connections:

    • Up to 10 concurrent connections per IP
    • Effective throughput: 5,000 emails concurrently
    • Recommended batch pacing: 20K emails/hour initially
  • API Rate Limits:

    • Not explicitly documented, but reasonable throttling applied
    • No published RPM/RPS limits (contact support for high-volume requirements)

PASS 2: Primary Analysis - Transactional Architecture

2.1 Transactional Email Focus

Postmark's entire platform is architected specifically for transactional email delivery. This specialization provides significant advantages:

Defining Transactional Email:

  • One-to-one messaging (not broadcast)
  • User-triggered (password reset, order confirmation, invoice)
  • Expected by recipient (not marketing)
  • Time-sensitive (immediate delivery critical)
  • Low volume per recipient (typically 1-5 per user per month)

Postmark Use Cases:

  1. Authentication & Account Management

    • Password reset links (time-sensitive, security-critical)
    • Email verification codes
    • Two-factor authentication codes
    • Account confirmation emails
    • Login alerts
  2. Transactional Notifications

    • Order confirmations
    • Shipment tracking updates
    • Invoice generation and delivery
    • Receipt emails
    • Subscription status changes
    • Payment confirmations
  3. System Alerts

    • Error notifications
    • Deployment alerts
    • Uptime monitoring notifications
    • Resource usage alerts
    • Security incident alerts
  4. User-Triggered Communications

    • Welcome emails
    • Account activation
    • Contact form submissions
    • Application status updates
    • Appointment confirmations

2.2 Message Streams: Transactional vs Broadcast

Transactional Message Stream:

- Infrastructure: Dedicated transactional sending IPs
- Purpose: One-to-one, user-triggered emails
- Default Subscription: "None" (no unsubscribe link required)
- Sender Reputation: Built independently from broadcast
- Use Case: Password resets, invoices, alerts
- Delivery Expectation: < 1 second
- Compliance: No CAN-SPAM/GDPR marketing requirements

Broadcast Message Stream:

- Infrastructure: Separate broadcast sending IPs
- Purpose: One-to-many, bulk communications
- Default Subscription: "Postmark" (auto-unsubscribe links)
- Sender Reputation: Independent from transactional
- Use Case: Newsletters, announcements, marketing
- Delivery Expectation: Within minutes
- Compliance: CAN-SPAM, GDPR marketing list requirements

Critical Separation Logic:

DO NOT MIX TRANSACTIONAL AND BROADCAST TRAFFIC
- Each stream has dedicated infrastructure
- Separate sending IPs prevent reputation bleed
- Broadcast bounces don't affect transactional delivery
- ISPs treat them differently (different authentication requirements)
- Best practice: Use different domains (transactional.example.com vs mail.example.com)

API Implementation:

// Sending through transactional stream
POST /email (or /email/withTemplate)
Headers: {
  "X-Postmark-Server-Token": "transactional-server-token"
}

// Sending through broadcast stream (if configured)
POST /email (different server with broadcast token)
Headers: {
  "X-Postmark-Server-Token": "broadcast-server-token"
}

2.3 Template System Architecture

Postmark uses Mustachio (lightweight handlebars variant) as its template engine.

Template Structure:

<h1>Hello {{Name}}</h1>
<p>Welcome {{Email}}!</p>
<ul>
  {{#each Items}}
    <li>{{this}}</li>
  {{/each}}
</ul>

Key Features:

  • Variables: {{variable_name}} for dynamic content
  • Iterators: {{#each array}}...{{/each}}
  • Conditionals: {{#if condition}}...{{/if}}
  • Partials: Not supported in base system (use MailMason for advanced templates)
  • Automatic CSS Inlining: Styles automatically inlined for email client compatibility
  • Responsive Design: Templates auto-optimize for mobile

Template Management:

- Create in Postmark UI or via API
- Store up to 100 templates per server
- Template aliases for easier reference
- Built-in preview with sample data
- Test delivery before production use
- Version control through API (update/replace workflow)

Validation Endpoint:

POST /templates/validate

Request: {
  "Subject": "Hello {{Name}}",
  "HtmlBody": "<p>Your code: {{Code}}</p>",
  "TextBody": "Your code: {{Code}}",
  "TemplateModel": {
    "Name": "John",
    "Code": "123456"
  }
}

Response: {
  "AllContentIsValid": true,
  "SuggestedTemplateModel": {
    "Name": "string",
    "Code": "string"
  },
  "HtmlBody": [success/error details],
  "TextBody": [success/error details],
  "Subject": [success/error details]
}

Template Layouts (Reusable Components): Layouts enable teams to define common header/footer templates:

Master Layout Template:
<html>
  <header>{{{header}}}</header>
  <body>{{{body}}}</body>
  <footer>{{{footer}}}</footer>
</html>

Individual Email Template:
{{{#layout "main"}}}
  <p>Email content here</p>
{{{/layout}}}

2.4 Deliverability Analytics

Tracking Features:

  1. Open Tracking

    • Enable with "TrackOpens": true
    • Postmark adds invisible 1x1 pixel to HTML emails
    • Records: First open, multiple opens, device/client info
    • Webhook notification on open event
    • Analytics dashboard for open rates
  2. Link Tracking

    • Enable with "TrackLinks": "HtmlAndText" or "HtmlOnly"
    • All links wrapped with click-tracking URLs
    • Records: Time of click, client, device
    • Webhook notification on click event
    • Analytics showing most-clicked links
  3. Delivery Tracking

    • Automatic on all emails
    • Delivery webhook notifies when email hits ISP server
    • Not same as "opened" (email accepted ≠ user receipt)
    • Includes ISP confirmation for each recipient
  4. Bounce Management

    • Automatic bounce detection
    • Types: Hard (permanent), Soft (temporary), Spam complaint, Transient
    • Bounce list automatically suppressed
    • Bounce webhook for real-time notification
    • Bounce reactivation available for verified addresses

Transparency Dashboard: Postmark publishes real-time delivery metrics:

  • Time to Inbox: Duration from request to user's inbox
  • Delivery Rates: Percentage of emails reaching inboxes vs bounce/spam
  • ISP Breakdown: Gmail, Yahoo, Outlook, iCloud, AOL performance
  • Update Frequency: Every 5 minutes (real-time transparency)
  • Public Status: https://status.postmarkapp.com

PASS 3: Rigor & Refinement - Performance Metrics & Details

3.1 Delivery Performance Standards

Industry-Leading Speed:

Metric Postmark Industry Typical
Median Delivery < 100ms 1-5 seconds
95th Percentile < 500ms 10-30 seconds
99th Percentile < 1 second 45-120 seconds
Max Observed < 10 seconds 5+ minutes

Postmark Philosophy:

  • Founder Chris Nagele: "Getting to the inbox extremely fast isn't optional—it's imperative"
  • Even 30-second delays trigger escalation procedures
  • Real business impact: One music e-commerce company needed expanded support teams due to slow purchase confirmation emails

Performance Testing: Independent testing (Labnify, NotificationAPI) shows:

  • Postmark: Consistent < 10 seconds, usually < 1 second
  • MailerSend: Occasionally 30-45 seconds during peak hours
  • SendGrid: Highly variable, 5-120 seconds
  • AWS SES: 10-60 seconds depending on configuration

3.2 Bounce & Spam Complaint Handling

Bounce Classifications:

  1. Hard Bounce (Permanent)

    • Invalid email address
    • Domain doesn't exist
    • Recipient closed account
    • Action: Permanently suppressed
  2. Soft Bounce (Temporary)

    • Mailbox full
    • Server temporarily unavailable
    • Message too large
    • Action: Retried automatically, may suppress after multiple failures
  3. Spam Complaint

    • User marked email as spam
    • ISP flagged as spam
    • Action: Immediately suppressed, reputation impact
    • Webhook: Spam complaint notification
  4. Transient Bounce

    • Connection timeout
    • DNS failure
    • Service temporarily unavailable
    • Action: Auto-retry with backoff

Bounce Suppression Behavior:

Hard Bounce → Immediate suppression, indefinite
Soft Bounce (3+ failures) → Suppression after threshold
Spam Complaint → Immediate suppression, indefinite

API Bounce Query:

GET /bounces?type=HardBounce&count=100

Response:
{
  "Bounces": [
    {
      "ID": 12345,
      "Type": "HardBounce",
      "MessageID": "msg-id-123",
      "Description": "The mailbox does not exist",
      "Details": "550 5.1.2 The email account that you tried to reach does not exist",
      "Email": "invalid@example.com",
      "BouncedAt": "2024-01-15T10:30:00Z",
      "Tag": "password-reset"
    }
  ]
}

Bounce Reactivation:

// Check if address can be reactivated
GET /bounces/activation?email=previously-bounced@example.com

// Activate address for resend
PUT /bounces/{bounceid}/activate

// Only available after 24 hours for spam complaints

How Link Tracking Works:

  1. URL Rewriting:

    • Original: https://example.com/confirm?code=123
    • Tracked: https://click.postmarkapp.com/ls/click?s=xxxxx&c=12345
    • Redirect maintained (transparent to user)
  2. Click Event Capture:

    • Records timestamp
    • Device type (desktop, mobile, tablet)
    • Email client (Gmail, Outlook, iPhone Mail, etc.)
    • IP address
    • User agent
  3. Multiple Clicks:

    • Each click recorded separately
    • Analytics show click count and unique clickers
    • First click and last click timestamps

API Configuration:

{
  "From": "sender@example.com",
  "To": "user@example.com",
  "Subject": "Confirm Your Email",
  "HtmlBody": "<a href='https://example.com/confirm?code=123'>Click here</a>",
  "TrackLinks": "HtmlAndText",  // or "HtmlOnly"
  "TrackOpens": true
}

Webhook Response for Click:

{
  "RecordType": "Click",
  "Recipient": "user@example.com",
  "MessageID": "msg-id-123",
  "MessageStream": "transactional",
  "ClickedAt": "2024-01-15T10:30:45Z",
  "OriginalLink": "https://example.com/confirm?code=123",
  "Client": {
    "Name": "Gmail",
    "Family": "Webmail",
    "Type": "Webmail"
  },
  "OS": {
    "Name": "OS X",
    "Family": "OS X"
  },
  "Platform": "Web"
}

3.4 Open Tracking Details

Open Tracking Implementation:

  • 1x1 transparent pixel added to HTML emails
  • Pixel loads when email is opened/previewed
  • Records time, device, email client
  • Works with most email clients (some disable images)

Client Compatibility:

  • Gmail: Full support (always loads images)
  • Outlook: Full support
  • Apple Mail: Full support
  • Yahoo: Full support
  • Mobile clients: Mostly supported
  • Text-only viewers: No open tracking

Privacy Considerations:

  • Some email clients load images by default (Gmail)
  • Others require user action (Outlook, Apple Mail)
  • Open tracking = "engaged with email," not "actually read"
  • Postmark discloses tracking to end users (ethical practice)

Analytics Webhook:

{
  "RecordType": "Open",
  "Recipient": "user@example.com",
  "MessageID": "msg-id-123",
  "UtcOpenedAt": "2024-01-15T10:30:45Z",
  "Client": {
    "Name": "Gmail",
    "Family": "Webmail"
  },
  "OS": {
    "Name": "iOS",
    "Family": "iOS"
  },
  "UserAgent": "Apple Mail"
}

PASS 4: Cross-Domain Analysis - Pricing & Competitive Positioning

4.1 Pricing Structure

Postmark Pricing Model: Subscription-based with volume tiers, no per-email overage charges after plan limit.

Plan Monthly Cost Email Limit Cost per 1K Max Users Domains
Developer Free 100 N/A 1 1
Basic $15 10,000 $1.50 4 5
Pro $16.50 10,000 $1.65 6 10
Platform $18 10,000 $1.80 Unlimited Unlimited

Add-On Costs:

  • Dedicated IP: $50/month per IP (requires 300K+ emails/month)
  • DMARC Monitoring: $14/month per domain
  • Custom Retention: $5/month (reduce from 45 to 7 or 28 days)
  • Additional Users: Included in plan tiers
  • API Access: Unlimited in all plans

Overage Pricing (above plan limits):

  • Basic: $1.80 per 1,000 additional emails
  • Pro: $1.30 per 1,000 additional emails
  • Platform: $1.20 per 1,000 additional emails

Cost Calculation Examples:

Example 1: 50,000 emails/month

Plan: Basic ($15/month for 10K)
Overage: 40,000 emails × ($1.80/1000) = $72
Total: $15 + $72 = $87/month = $0.00174 per email

Example 2: 500,000 emails/month

Plan: Platform ($18/month)
Need to scale to higher tier or negotiate
Estimated: $200-300/month (contact sales)
Cost per email: $0.0004-0.0006

4.2 Competitive Comparison

vs. AWS SES (Amazon Simple Email Service)

Aspect AWS SES Postmark
Pricing $0.10/1K emails $15/month (10K emails)
Free Tier 62K emails/month for 12 months 100 emails/month (perpetual)
Delivery Self-managed, variable Managed, < 1 second
Bounce Handling Manual via SNS Automatic + API
Analytics Requires CloudWatch setup Built-in dashboard
Support AWS Support plans only Ticket support included
Setup Complexity High (requires SNS/SQS) Low (API directly)
Ideal For High-volume, cost-sensitive Time-sensitive, reliability-focused

Cost Comparison (1M emails/month):

  • AWS SES: $100 (minimal) + infrastructure costs
  • Postmark: Requires enterprise plan, ~$500-1000/month

vs. SendGrid

Aspect SendGrid Postmark
Pricing Tiered, starts at $19.95/month Simpler, starts at $15/month
Email Types Transactional + Marketing Transactional focused
Features Comprehensive (marketing automation) Core transactional + tracking
Delivery Speed 5-120 seconds variable < 1 second consistent
Bounce Management Manual suppression lists Automatic + detailed API
API Complexity Higher (more options) Lower (simpler API)
Best For Multi-channel marketing Pure transactional delivery
Enterprise Support Available, additional cost Included at higher tiers

vs. Mailgun

Aspect Mailgun Postmark
Pricing $35-100/month (higher) $15-18/month (lower)
Free Tier No free tier 100 emails/month free
Inbound Email Supported Supported
Message Forwarding Advanced Not included
Webhook Reliability 3-day retry Extended retry schedule
Developer Focus Very high High
Best For Complex email workflows Straightforward transactional

4.3 Cost Analysis: Total Cost of Ownership

Postmark Implementation Cost:

Development:
- API integration: 4-8 hours @ $50/hr = $200-400
- Template creation: 2-4 hours = $100-200
- Webhook setup: 2-3 hours = $100-150
- Testing/validation: 3-5 hours = $150-250
Total Dev: $550-1000

Monthly Operational:
- Plan cost: $15-18 (basic)
- Additional features: $0-20
- Monitoring overhead: < 1 hour/month
Total Monthly: $15-38
Total Annual: $180-456

AWS SES Implementation Cost:

Development:
- API integration: 6-10 hours = $300-500
- SNS/SQS setup: 4-8 hours = $200-400
- Lambda bounce handler: 4-6 hours = $200-300
- Monitoring/alerting: 4-6 hours = $200-300
Total Dev: $900-1500

Monthly Operational:
- Bare SES cost: $100 (for 1M emails)
- CloudWatch monitoring: $5-10
- Lambda execution: $10-20
- Database storage (bounce lists): $10-20
Total Monthly: $125-150
Total Annual: $1500-1800

Break-even Analysis:

  • < 100K emails/month: Use Postmark
  • 100K-1M emails/month: Consider both (Postmark + dev time vs. SES infrastructure)
  • 1M emails/month: AWS SES likely more cost-effective

4.4 Market Positioning

Postmark's Competitive Advantages:

  1. Speed: Industry-leading < 1 second delivery
  2. Simplicity: Purpose-built for transactional, fewer options = easier setup
  3. Transparency: Public performance metrics updated every 5 minutes
  4. Support: Ticket support included at all tiers (not AWS model)
  5. Reliability: 99.99% uptime SLA
  6. Compliance: GDPR-ready, DPA included, no hidden restrictions

Trade-offs:

  1. No Marketing Features: SendGrid has automation, segmentation, A/B testing
  2. Email Volume Limits: Higher costs at massive scale (> 5M emails/month)
  3. Inbound Email: Limited features compared to Mailgun
  4. Customization: Less flexible than AWS SES (good for simple, bad for complex)

PASS 5: Framework Mapping - InfraFabric Integration Patterns

5.1 Transactional Notification Architecture

InfraFabric systems use Postmark for critical user-triggered notifications:

Pattern 1: Password Reset Flow

User Action: "Forgot Password"
  ↓
Application generates reset token
  ↓
Call Postmark API (Template: password-reset)
  POST /email/withTemplate
  {
    "From": "noreply@example.com",
    "To": "user@example.com",
    "TemplateId": 12345,
    "TemplateModel": {
      "UserName": "John Doe",
      "ResetLink": "https://example.com/reset?token=xyz",
      "ExpirationTime": "1 hour"
    },
    "TrackLinks": "HtmlAndText",
    "Tag": "password-reset"
  }
  ↓
Postmark returns MessageID immediately
  ↓
Application logs sent status
  ↓
Postmark webhook (delivery) confirms inbox delivery
  ↓
Postmark webhook (click) tracks if user clicked reset link
  ↓
User completes password reset

Pattern 2: Invoice/Receipt Delivery

Trigger: Order completion
  ↓
Generate invoice PDF, store in S3
  ↓
Call Postmark with attachment
  POST /email
  {
    "From": "invoices@example.com",
    "To": "customer@example.com",
    "Subject": "Invoice #INV-2024-001",
    "TemplateId": 54321,
    "TemplateModel": {
      "OrderNumber": "ORD-2024-001",
      "Total": "$150.00",
      "Date": "2024-01-15"
    },
    "Attachments": [{
      "Name": "invoice.pdf",
      "Content": "[base64-encoded-pdf]",
      "ContentType": "application/pdf"
    }],
    "TrackOpens": true,
    "Tag": "invoice"
  }
  ↓
Postmark delivers to inbox
  ↓
Analytics webhook tracks when customer views invoice

Pattern 3: Multi-User Alert Broadcasting (via separate Broadcast stream)

Alert Trigger: Security incident detected
  ↓
Query all admin users (< 50 recipients)
  ↓
Send via Broadcast Message Stream
  POST /email
  Headers: {"X-Postmark-Server-Token": "broadcast-token"}
  {
    "From": "alerts@example.com",
    "To": "admin1@example.com, admin2@example.com, admin3@example.com",
    "Subject": "Security Alert: Suspicious Activity Detected",
    "TemplateId": 99999,
    "MessageStream": "broadcast"
  }
  ↓
Broadcast stream maintains separate reputation
  ↓
Webhook notifies on delivery

5.2 Template Management Strategy

InfraFabric Template Hierarchy:

Level 1: Master Layouts (Reusable)
├── email-base.layout
│   └── Common header, footer, styles
├── alert-layout.layout
│   └── Alert-specific formatting
└── transaction-layout.layout
    └── Transaction-specific header/footer

Level 2: Email Templates (Specific)
├── password-reset.template
│   └── Uses: email-base.layout
├── invoice.template
│   └── Uses: transaction-layout.layout
├── welcome.template
│   └── Uses: email-base.layout
└── alert-notification.template
    └── Uses: alert-layout.layout

Level 3: Variables (Template Model)
└── Password Reset Model:
    {
      "UserName": "string",
      "ResetLink": "string",
      "ExpirationMinutes": "number",
      "SupportEmail": "string"
    }

Template Validation CI/CD Integration:

#!/bin/bash
# pre-commit hook: validate all templates

for template_file in templates/*.json; do
  template_id=$(jq -r '.TemplateId' "$template_file")

  curl -X POST https://api.postmarkapp.com/templates/validate \
    -H "X-Postmark-Server-Token: ${POSTMARK_TOKEN}" \
    -d @"$template_file" > /dev/null

  if [ $? -ne 0 ]; then
    echo "Template validation failed: $template_file"
    exit 1
  fi
done

5.3 Webhook Event Processing Pipeline

Postmark → Message Queue → Event Handler

Postmark Webhook Event
  ↓
POST https://example.com/webhooks/postmark
  {
    "RecordType": "Bounce",
    "MessageID": "msg-123",
    "Email": "invalid@example.com",
    "BouncedAt": "2024-01-15T10:30:00Z",
    "Type": "HardBounce"
  }
  ↓
API receives, validates signature
  ↓
Enqueue to message queue (RabbitMQ/Kafka)
  ↓
Event handlers process async:
  ├── BounceHandler: Suppress email in app DB
  ├── AnalyticsHandler: Update bounce statistics
  ├── AlertHandler: Notify support if high bounce rate
  └── NotificationHandler: Alert user if critical service
  ↓
Handlers update database, cache, send notifications

Example Webhook Handler (Node.js):

app.post('/webhooks/postmark', async (req, res) => {
  const event = req.body;

  // Validate webhook signature (optional but recommended)
  // const signature = req.headers['x-postmark-signature'];
  // validateSignature(event, signature);

  try {
    switch(event.RecordType) {
      case 'Bounce':
        await handleBounce(event);
        break;
      case 'Delivery':
        await handleDelivery(event);
        break;
      case 'Open':
        await handleOpen(event);
        break;
      case 'Click':
        await handleClick(event);
        break;
      case 'SpamComplaint':
        await handleSpamComplaint(event);
        break;
      default:
        console.warn('Unknown event type:', event.RecordType);
    }
    res.status(200).json({ success: true });
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).json({ error: error.message });
  }
});

async function handleBounce(event) {
  // Store bounce in database
  await db.bounces.create({
    email: event.Email,
    bounceType: event.Type,
    reason: event.Description,
    messageId: event.MessageID,
    bouncedAt: event.BouncedAt,
    tag: event.Tag
  });

  // Suppress email in user suppression list
  await db.users.update(
    { email: event.Email },
    { bounced: true, bounceType: event.Type }
  );

  // Alert support for spam complaints
  if (event.Type === 'SpamComplaint') {
    await notificationService.alert(
      `User reported email as spam: ${event.Email}`,
      'HIGH'
    );
  }
}

async function handleClick(event) {
  // Log click event for analytics
  await db.analytics.create({
    type: 'click',
    messageId: event.MessageID,
    email: event.Recipient,
    link: event.OriginalLink,
    clickedAt: event.ClickedAt,
    client: event.Client.Name,
    device: event.Platform
  });
}

PASS 6: Specification - API Details & Implementation

6.1 Complete Email API Specification

Single Email Endpoint:

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

Required Headers:

X-Postmark-Server-Token: [your-server-api-token]
Content-Type: application/json
Accept: application/json

Request Schema:

{
  "From": "sender@example.com",
  "To": "recipient@example.com",
  "Cc": "cc@example.com",
  "Bcc": "bcc@example.com",
  "Subject": "Email Subject",
  "Tag": "email-tag",
  "HtmlBody": "<html><body>Content</body></html>",
  "TextBody": "Plain text content",
  "ReplyTo": "reply@example.com",
  "Headers": [
    {
      "Name": "X-Custom-Header",
      "Value": "custom-value"
    }
  ],
  "TrackOpens": true,
  "TrackLinks": "HtmlAndText",
  "Metadata": {
    "user_id": "123",
    "order_id": "456"
  },
  "Attachments": [
    {
      "Name": "document.pdf",
      "Content": "[base64-encoded-content]",
      "ContentType": "application/pdf"
    }
  ],
  "MessageStream": "transactional"
}

Field Specifications:

Field Type Required Notes
From string Yes Must be a confirmed Sender Signature
To string Yes Single or multiple email addresses
Cc string No Secondary recipients
Bcc string No Hidden recipients (up to 50 total recipients)
Subject string Yes Max 2,000 characters
HtmlBody string No Max 5 MB, one of HtmlBody or TextBody required
TextBody string No Plain text fallback
ReplyTo string No Reply-to address
Tag string No For categorization (analytics, bounce filtering)
Headers array No Custom email headers
TrackOpens boolean No Default: false
TrackLinks string No "HtmlAndText", "HtmlOnly", or "None"
Metadata object No Custom JSON (up to 4 KB)
Attachments array No Base64-encoded files (max 10 MB total)
MessageStream string No Default: "transactional"

Success Response (200 OK):

{
  "To": ["recipient@example.com"],
  "Cc": [],
  "Bcc": [],
  "SubmittedAt": "2024-01-15T10:30:00Z",
  "MessageID": "00000000-0000-0000-0000-000000000000",
  "ErrorCode": 0
}

Error Responses:

400 Bad Request
{
  "ErrorCode": 422,
  "Message": "Invalid email address specified (From)",
  "Details": "The From address you supplied is not valid."
}

401 Unauthorized
{
  "ErrorCode": 10,
  "Message": "The X-Postmark-Server-Token header does not contain a valid server token."
}

429 Too Many Requests
{
  "ErrorCode": 300,
  "Message": "You have exceeded the maximum number of requests per second (n) allowed. Please retry your requests at a slower rate."
}

500 Internal Server Error
{
  "ErrorCode": 1,
  "Message": "An error message describing what went wrong."
}

6.2 Batch Sending Specification

Batch Email Endpoint:

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

Request Schema:

{
  "Messages": [
    {
      "From": "sender@example.com",
      "To": "recipient1@example.com",
      "Subject": "Subject 1",
      "HtmlBody": "<p>Body 1</p>",
      "Tag": "batch-send"
    },
    {
      "From": "sender@example.com",
      "To": "recipient2@example.com",
      "Subject": "Subject 2",
      "HtmlBody": "<p>Body 2</p>",
      "Tag": "batch-send"
    }
  ]
}

Batch Constraints:

  • Maximum 500 messages per request
  • Maximum 50 MB total payload
  • Return status 200 even if some messages fail
  • Must check individual message ErrorCode

Response Schema:

[
  {
    "ErrorCode": 0,
    "Message": "OK",
    "MessageID": "00000000-0000-0000-0000-000000000001",
    "SubmittedAt": "2024-01-15T10:30:00Z",
    "To": ["recipient1@example.com"]
  },
  {
    "ErrorCode": 422,
    "Message": "The To address you supplied is not a valid email address",
    "SubmittedAt": "2024-01-15T10:30:00Z",
    "To": null
  }
]

Batch Optimization:

// Optimal batch processing for large volumes
async function sendLargeBatch(recipients) {
  const BATCH_SIZE = 500;
  const BATCHES_PER_SECOND = 1; // Postmark recommendation

  for (let i = 0; i < recipients.length; i += BATCH_SIZE) {
    const batch = recipients.slice(i, i + BATCH_SIZE);
    const messages = batch.map(r => ({
      From: 'noreply@example.com',
      To: r.email,
      Subject: 'Important notification',
      TemplateId: 12345,
      TemplateModel: {
        name: r.name,
        customData: r.customData
      },
      MessageStream: 'broadcast'
    }));

    try {
      const response = await postmark.sendEmailBatch(messages);

      // Check for per-message errors
      response.forEach((result, index) => {
        if (result.ErrorCode !== 0) {
          console.error(`Failed for ${batch[index].email}: ${result.Message}`);
        }
      });

      // Wait before next batch (1 batch/sec = 500 emails/sec)
      await sleep(1000);
    } catch (error) {
      console.error('Batch send failed:', error);
      // Implement exponential backoff retry
    }
  }
}

6.3 Template API Specification

Send Email with Template:

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

Request Schema:

{
  "From": "sender@example.com",
  "To": "recipient@example.com",
  "TemplateId": 12345,
  "TemplateModel": {
    "UserName": "John",
    "ResetLink": "https://example.com/reset?code=xyz",
    "ExpirationMinutes": 60
  },
  "Tag": "password-reset",
  "TrackOpens": true,
  "Metadata": {
    "user_id": "123"
  }
}

Batch with Templates:

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

Request Schema:

{
  "Messages": [
    {
      "From": "sender@example.com",
      "To": "user1@example.com",
      "TemplateId": 12345,
      "TemplateModel": {
        "UserName": "John",
        "ResetLink": "https://example.com/reset?code=abc"
      }
    },
    {
      "From": "sender@example.com",
      "To": "user2@example.com",
      "TemplateId": 12345,
      "TemplateModel": {
        "UserName": "Jane",
        "ResetLink": "https://example.com/reset?code=def"
      }
    }
  ]
}

Template CRUD Operations:

// Create template
POST /templates
{
  "Name": "Password Reset",
  "Subject": "Reset Your Password",
  "HtmlBody": "<p>Hi {{UserName}}, click <a href='{{ResetLink}}'>here</a> to reset</p>",
  "TextBody": "Reset link: {{ResetLink}}",
  "TemplateType": "Standard"
}

// List templates
GET /templates?count=100&offset=0

// Get specific template
GET /templates/12345

// Update template
PUT /templates/12345
{
  "Name": "Updated Password Reset",
  "Subject": "New subject"
}

// Delete template
DELETE /templates/12345

// Validate template
POST /templates/validate
{
  "Subject": "Hello {{Name}}",
  "HtmlBody": "<p>{{Content}}</p>",
  "TextBody": "{{Content}}",
  "TemplateModel": {
    "Name": "Test",
    "Content": "Test content"
  }
}

6.4 Message Streams API

List Streams:

GET /message-streams?messagetype=transactional

Response:

{
  "MessageStreams": [
    {
      "ID": "transactional",
      "Name": "Transactional",
      "Description": "Transactional email stream",
      "MessageStreamType": "Transactional",
      "CreatedAt": "2024-01-01T00:00:00Z",
      "ArchivedAt": null,
      "SubscriptionManagementConfiguration": {
        "UnsubscribeHandlingType": "None"
      }
    }
  ]
}

Create Broadcast Stream:

POST /message-streams
{
  "ID": "broadcasts",
  "Name": "Broadcast Stream",
  "MessageStreamType": "Broadcasts",
  "Description": "For bulk communications",
  "SubscriptionManagementConfiguration": {
    "UnsubscribeHandlingType": "Postmark"
  }
}

Archive Stream:

POST /message-streams/broadcasts/archive
Response: { "ID": "broadcasts", "ArchivedAt": "2024-01-15T10:30:00Z" }

6.5 Bounce API Detailed Specification

Query Bounces:

GET /bounces?type=HardBounce&inactivemailboxes=false&count=100&offset=0&fromdate=2024-01-01&todate=2024-01-31

Parameters:

  • type: HardBounce, SoftBounce, SpamComplaint, or Transient
  • inactivemailboxes: Include/exclude transient bounces
  • count: Results per page (max 100)
  • offset: Pagination offset
  • fromdate: Start date (ISO 8601)
  • todate: End date (ISO 8601)

Bounce Details:

GET /bounces/123456

Response:
{
  "ID": 123456,
  "Type": "HardBounce",
  "Email": "invalid@example.com",
  "BouncedAt": "2024-01-15T10:30:00Z",
  "DumpAvailable": true,
  "Inactive": false,
  "CanActivate": true,
  "Subject": "Reset Your Password",
  "Name": "Bounce",
  "Description": "The mailbox does not exist",
  "Details": "550 5.1.2 bad destination mailbox address",
  "MessageID": "00000000-0000-0000-0000-000000000000",
  "Tag": "password-reset",
  "MailboxHash": "abc123"
}

Bounce Reactivation:

PUT /bounces/123456/activate

Response:
{
  "Bounce": { /* bounce object */ },
  "Message": "The bounce activation was successful."
}

PASS 7: Meta-Validation - Best Practices & Standards Verification

7.1 Authentication & Security Best Practices

API Token Management:

DO:
✓ Store tokens in environment variables (not in code)
✓ Use different tokens for production/development
✓ Rotate tokens every 90 days
✓ Limit token scope to specific servers/streams
✓ Use X-Postmark-Server-Token for most operations
✓ Use X-Postmark-Account-Token only when necessary

DON'T:
✗ Commit tokens to version control
✗ Use same token across environments
✗ Share tokens in logs or error messages
✗ Pass tokens in URLs (always in headers)
✗ Use overly permissive token scopes

SMTP Token Configuration:

Environment Variables:
POSTMARK_SMTP_USER=your-access-key
POSTMARK_SMTP_PASS=your-secret-key
POSTMARK_SMTP_HOST=smtp.postmarkapp.com
POSTMARK_SMTP_PORT=587  # TLS recommended

Connection String:
smtp://[access-key]:[secret-key]@smtp.postmarkapp.com:587

TLS/SSL Requirements:

✓ Always use TLS for SMTP (port 587 with STARTTLS)
✓ Verify server certificates
✓ Never send credentials over unencrypted connections
✓ Monitor for certificate expiration

7.2 Email Authentication Standards

SPF (Sender Policy Framework):

DNS Record Format:
v=spf1 include:postmarkapp.com ~all

Example:
example.com TXT "v=spf1 include:postmarkapp.com ~all"

Verification:
- Published at domain's DNS
- Tells ISPs which mail servers are authorized to send from your domain
- Postmark automatically configured when you add sender signature

DKIM (DomainKeys Identified Mail):

DNS Record Format (TXT):
DKIM_SELECTOR._domainkey.example.com TXT "[public-key-content]"

Postmark provides DNS records automatically:
1. Login to Postmark
2. Navigate to Sender Signatures → Domain
3. Copy DKIM DNS record
4. Add TXT record to your DNS provider
5. Verify in Postmark (takes up to 48 hours)

Verification command:
nslookup -type=TXT postmark._domainkey.example.com

DMARC (Domain-based Message Authentication):

DNS Record Format (TXT):
_dmarc.example.com TXT "v=DMARC1; p=quarantine; rua=mailto:admin@example.com"

Policy Options:
- p=none: Monitor only (receive reports)
- p=quarantine: ISPs mark suspicious emails as spam
- p=reject: ISPs reject authentication-failed emails

Postmark DMARC Service:
- Free DMARC monitoring at dmarc.postmarkapp.com
- Weekly digests with alignment and statistics
- Paid add-on: $14/month for enhanced monitoring

Authentication Alignment:

Postmark verification checklist:
☐ SPF record published and verified
☐ DKIM record published and verified
☐ DMARC policy configured
☐ From domain matches DKIM signing domain
☐ Return-Path domain configured
☐ Test with mail authentication tools:
  - MXToolbox: https://mxtoolbox.com
  - Mail-tester: https://www.mail-tester.com
  - DM MARC quarantine tester

7.3 Deliverability Best Practices

Pre-Sending Checklist:

  1. Sender Verification

    ☐ Sender email confirmed in Postmark
    ☐ Domain DKIM/SPF verified
    ☐ Return-Path configured (optional but recommended)
    ☐ Reply-To set if different from From
    
  2. Message Content

    ☐ Clear unsubscribe link (for broadcasts only)
    ☐ Legitimate business purpose
    ☐ No phishing indicators
    ☐ Balanced text-to-image ratio (avoid 100% image)
    ☐ No suspicious links or redirects
    ☐ HTML validates (no broken tags)
    ☐ Mobile-responsive design
    
  3. List Hygiene

    ☐ Remove hard bounces (from Postmark bounce API)
    ☐ Remove spam complaints
    ☐ Remove unsubscribed addresses
    ☐ Validate emails before sending
    ☐ Use double opt-in for new subscribers (marketing only)
    ☐ Monitor soft bounce trends
    
  4. Performance Tuning

    ☐ Use templates for consistent HTML
    ☐ Limit to 50 recipients per message (max)
    ☐ Use batch endpoint for volumes > 1K
    ☐ Space large broadcasts (50K+ emails/hour)
    ☐ Monitor Time to Inbox metric
    

ISP-Specific Considerations:

ISP Consideration Best Practice
Gmail Always loads images Open tracking reliable
Yahoo Reputation-sensitive Maintain clean bounce list
Outlook DKIM-sensitive Verify DKIM strictly
AOL Size-limited Keep message compact
iCloud Privacy-focused No excessive tracking

7.4 Webhook Security & Validation

Webhook Signature Validation (recommended):

const crypto = require('crypto');

function validatePostmarkWebhookSignature(req, secret) {
  const signature = req.headers['x-postmark-signature'];
  if (!signature) return false;

  const body = typeof req.body === 'string'
    ? req.body
    : JSON.stringify(req.body);

  const computed = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computed)
  );
}

app.post('/webhook/postmark', (req, res) => {
  if (!validatePostmarkWebhookSignature(req, process.env.POSTMARK_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook
  handleWebhookEvent(req.body);
  res.status(200).json({ success: true });
});

Webhook Retry Policy:

Bounce/Inbound webhooks:
1 minute → 5 minutes → 10 minutes → 15 minutes → 20 minutes →
30 minutes → 1 hour → 2 hours → 4 hours → 6 hours

Other webhooks (click, open, delivery):
1 minute → 5 minutes → 15 minutes → then stops

Success: HTTP 200-299
Failure: Any other status (will retry)
Stop: HTTP 403 (explicit opt-out)

Webhook Best Practices:

✓ Always validate webhook signatures
✓ Return 200 status immediately (process async)
✓ Enqueue to message queue for processing
✓ Implement idempotency (same webhook called twice should be safe)
✓ Monitor webhook response times (< 1 second)
✓ Log all webhook events for debugging
✓ Test with RequestBin or similar before production
✓ Keep webhook URL accessible from internet
✓ Use HTTPS (required by Postmark)

PASS 8: Deployment Planning - Production Configuration & Monitoring

8.1 Sender Signature Setup Process

Step 1: Create Sender Signature (UI)

1. Login to Postmark dashboard
2. Click "Sender Signatures" in left menu
3. Click "Add Sender Signature"
4. Enter sender email (e.g., noreply@example.com)
5. Enter sender name (e.g., "Example Inc")
6. Verify email (click link in confirmation email)

Step 2: Configure DKIM (Recommended)

1. In Sender Signature, click "DNS Settings"
2. Copy DKIM record provided (e.g., postmark._domainkey.example.com)
3. Add to your DNS provider:
   Host: postmark._domainkey.example.com
   Type: TXT
   Value: [Postmark-provided-value]
4. Click "Verify DNS" in Postmark (usually 5-60 minutes)
5. Confirm verification shows "Verified"

Step 3: Configure Return-Path (Optional but Recommended)

1. In Sender Signature, click "Return-Path Domain"
2. Copy CNAME record provided
3. Add to DNS:
   Host: [prefix].example.com (e.g., bounce.example.com)
   Type: CNAME
   Value: [Postmark-provided-value]
4. Verify in Postmark

API Endpoint: Create Sender Signature Programmatically

POST /senders
Headers: X-Postmark-Account-Token: [account-token]

Request:
{
  "FromEmail": "noreply@example.com",
  "Name": "Example Inc",
  "ReplyToEmail": "support@example.com",
  "ReturnPathDomain": "bounce.example.com"
}

Response:
{
  "ID": 123456,
  "Domain": "example.com",
  "EmailAddress": "noreply@example.com",
  "Name": "Example Inc",
  "ReplyToEmailAddress": "support@example.com",
  "Confirmed": false,
  "CreatedAt": "2024-01-15T10:30:00Z",
  "DKIMVerified": false,
  "DKIMTokens": ["token1", "token2"],
  "SafeToRemoveToken": "token1"
}

8.2 Message Stream Configuration

Transactional Stream Setup:

POST /message-streams

{
  "ID": "transactional",
  "Name": "Transactional Emails",
  "MessageStreamType": "Transactional",
  "Description": "Password resets, receipts, alerts",
  "SubscriptionManagementConfiguration": {
    "UnsubscribeHandlingType": "None"
  }
}

Note: Default transactional stream exists automatically

Broadcast Stream Setup (for marketing):

POST /message-streams

{
  "ID": "broadcasts",
  "Name": "Marketing Campaigns",
  "MessageStreamType": "Broadcasts",
  "Description": "Newsletters and announcements",
  "SubscriptionManagementConfiguration": {
    "UnsubscribeHandlingType": "Postmark"
  }
}

Inbound Stream Setup (for email parsing):

POST /message-streams

{
  "ID": "inbound",
  "Name": "Inbound Email",
  "MessageStreamType": "Inbound",
  "Description": "Parses incoming emails"
}

Note: Only one Inbound stream allowed per server

8.3 Webhook Configuration

Setup Bounce Webhook:

1. Login to Postmark
2. Select Server → Message Stream
3. Click "Webhooks" tab
4. Click "Add Webhook"
5. Enter URL: https://example.com/webhooks/postmark
6. Select event types: Bounce
7. Save

Configuration verification:
- Postmark will send test bounce event
- Monitor application logs for webhook receipt
- Verify bounce handling in application

Setup Delivery Webhook:

1. Click "Add Webhook"
2. Enter URL: https://example.com/webhooks/postmark/delivery
3. Select event types: Delivery
4. Save and test

Setup Click/Open Tracking Webhooks:

1. Click "Add Webhook"
2. Enter URL: https://example.com/webhooks/postmark/tracking
3. Select event types:
   ☐ Open
   ☐ Click
4. Configure tracking in email templates:
   "TrackOpens": true,
   "TrackLinks": "HtmlAndText"
5. Save and test

Webhook Testing Setup:

Using RequestBin for testing:
1. Visit https://requestbin.com
2. Create new bin (gets unique URL)
3. Configure in Postmark as webhook URL
4. Send test email
5. Inspect webhook payload in RequestBin
6. Switch to production URL once verified

Using curl simulation:
curl -X POST https://example.com/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "RecordType": "Bounce",
    "Type": "HardBounce",
    "Bounce": {
      "Email": "invalid@example.com",
      "BouncedAt": "2024-01-15T10:30:00Z"
    }
  }'

8.4 Monitoring & Alerting Strategy

Key Metrics to Monitor:

  1. Delivery Metrics

    - Messages sent per hour/day
    - Delivery rate (% successfully delivered)
    - Bounce rate (% bounced)
    - Time to inbox (monitor Postmark's published metrics)
    - Failed sends (API errors)
    
  2. Engagement Metrics

    - Open rate (% emails opened)
    - Click rate (% emails with clicks)
    - Most-clicked links (which CTAs work best)
    - Device types (mobile vs desktop opens)
    
  3. Health Metrics

    - Webhook delivery latency
    - Webhook error rate
    - Bounce list growth
    - Spam complaint rate
    - Hard bounce vs soft bounce ratio
    
  4. Business Metrics

    - Cost per email (total spend / emails sent)
    - Cost per successful delivery
    - Cost per engagement
    - Template usage patterns
    

Monitoring Implementation:

// Prometheus metrics example
const prometheus = require('prom-client');

const emailsSent = new prometheus.Counter({
  name: 'postmark_emails_sent_total',
  help: 'Total emails sent via Postmark'
});

const emailsDelivered = new prometheus.Counter({
  name: 'postmark_emails_delivered_total',
  help: 'Total emails successfully delivered'
});

const emailBounces = new prometheus.Counter({
  name: 'postmark_bounces_total',
  help: 'Total email bounces',
  labelNames: ['type']
});

const webhookLatency = new prometheus.Histogram({
  name: 'postmark_webhook_latency_ms',
  help: 'Webhook processing latency'
});

// Usage
emailsSent.inc();
emailsDelivered.inc();
emailBounces.labels('hard').inc();
webhookLatency.observe(processingTimeMs);

Alert Rules:

alerts:
  - name: HighBounceRate
    condition: bounce_rate > 5%
    severity: HIGH
    action: Page on-call engineer

  - name: HighSpamComplaintRate
    condition: spam_complaint_rate > 0.1%
    severity: CRITICAL
    action: Page on-call engineer immediately

  - name: WebhookProcessingFailed
    condition: webhook_error_rate > 1%
    severity: HIGH
    action: Alert engineering team

  - name: LongTimeToInbox
    condition: avg_time_to_inbox > 5s
    severity: MEDIUM
    action: Create incident, investigate

  - name: HighSoftBounceRate
    condition: soft_bounce_rate > 10%
    severity: MEDIUM
    action: Review email content, check sender reputation

8.5 CI/CD Integration

Pre-Deployment Validation:

#!/bin/bash
# deploy-postmark.sh

set -e

echo "Validating Postmark configuration..."

# 1. Check environment variables
if [ -z "$POSTMARK_SERVER_TOKEN" ]; then
  echo "ERROR: POSTMARK_SERVER_TOKEN not set"
  exit 1
fi

# 2. Validate sender signature
echo "Checking sender signatures..."
SENDERS=$(curl -s -H "X-Postmark-Account-Token: $POSTMARK_ACCOUNT_TOKEN" \
  https://api.postmarkapp.com/senders)

if echo "$SENDERS" | jq -e '.Senders | length > 0' > /dev/null; then
  echo "✓ Sender signatures configured"
else
  echo "ERROR: No sender signatures found"
  exit 1
fi

# 3. Validate templates
echo "Validating templates..."
for template_file in templates/*.json; do
  echo "  Validating $template_file..."
  VALIDATION=$(curl -s -X POST \
    -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
    -d @"$template_file" \
    https://api.postmarkapp.com/templates/validate)

  if ! echo "$VALIDATION" | jq -e '.AllContentIsValid == true' > /dev/null; then
    echo "ERROR: Template validation failed for $template_file"
    echo "$VALIDATION" | jq '.'
    exit 1
  fi
done

# 4. Test email delivery
echo "Sending test email..."
TEST_RESULT=$(curl -s -X POST \
  -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "From": "test@example.com",
    "To": "internal-test@example.com",
    "Subject": "Deployment test",
    "TextBody": "Test email from deployment pipeline"
  }' \
  https://api.postmarkapp.com/email)

if echo "$TEST_RESULT" | jq -e '.MessageID' > /dev/null; then
  echo "✓ Test email sent successfully"
else
  echo "ERROR: Test email failed"
  echo "$TEST_RESULT" | jq '.'
  exit 1
fi

# 5. Verify webhooks
echo "Checking webhook configuration..."
WEBHOOKS=$(curl -s -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
  https://api.postmarkapp.com/message-streams/transactional/webhooks)

if echo "$WEBHOOKS" | jq -e '.Webhooks | length > 0' > /dev/null; then
  echo "✓ Webhooks configured"
else
  echo "WARNING: No webhooks configured (may be intentional)"
fi

echo ""
echo "✓ All Postmark validations passed - safe to deploy"

8.6 Production Checklist

Pre-Production Deployment:

Infrastructure & Authentication
☐ API tokens securely stored in secrets management
☐ HTTPS enforced for all Postmark API calls
☐ Webhook URLs HTTPS-accessible
☐ Database backups configured

Email Configuration
☐ Sender signature created and verified
☐ DKIM record published and verified
☐ SPF record published
☐ DMARC policy configured (at minimum p=none)
☐ From domain matches DKIM signing domain
☐ Reply-To address configured (optional)
☐ Return-Path domain configured (optional)

Message Streams
☐ Transactional stream configured
☐ Broadcast stream created (if applicable)
☐ Different From addresses for transactional vs broadcast
☐ Message stream properly set in API calls

Templates
☐ All templates created in Postmark
☐ Templates validated with API
☐ Template variables match model structure
☐ HTML tested across email clients
☐ Mobile responsiveness verified
☐ CSS inlined automatically by Postmark

Webhooks
☐ All webhook URLs HTTPS and accessible
☐ Bounce webhook configured
☐ Delivery webhook configured (optional)
☐ Click/Open webhooks configured (if using tracking)
☐ Webhook handlers implement idempotency
☐ Webhook handlers return 200 immediately

Bounce Handling
☐ Hard bounce list queried regularly
☐ Spam complaints automatically suppressed
☐ Bounce list integrated into user suppression
☐ Manual bounce list cleanup process documented
☐ Bounce reactivation process documented

Monitoring
☐ Metrics collection configured
☐ Dashboards created for key metrics
☐ Alerting rules configured
☐ On-call escalation process defined
☐ Runbooks created for common issues

Testing
☐ Send test emails to real accounts
☐ Verify delivery within < 1 second
☐ Verify open tracking (if enabled)
☐ Verify click tracking (if enabled)
☐ Test bounce handling (send to invalid address)
☐ Test webhook delivery
☐ Load test with 100+ concurrent sends
☐ Test error handling (invalid from address, etc)

Documentation
☐ API integration documented
☐ Template management process documented
☐ Webhook processing flow documented
☐ Runbook for common issues created
☐ Team trained on Postmark features

Test Scenarios (8+ Comprehensive Tests)

Test 1: Basic Email Sending

Description: Send single transactional email
Setup:
  - Valid sender signature configured
  - Test recipient email address

Test Steps:
  1. POST /email with valid request
  2. Verify 200 response with MessageID
  3. Check email delivered within 1 second
  4. Verify email content intact
  5. Confirm no bounce notification

Expected Result: Email delivered successfully
Success Criteria: MessageID returned, email in inbox within 1 second

Test 2: Batch Sending Performance

Description: Send 500 emails in single batch request
Setup:
  - 500 unique valid recipient addresses
  - Batch message payload < 50 MB

Test Steps:
  1. POST /email/batch with 500 messages
  2. Verify 200 response within 2 seconds
  3. Verify all 500 MessageIDs in response
  4. Check response for any error codes per message
  5. Monitor delivery over next 5 seconds
  6. Confirm all 500 delivered

Expected Result: Batch processed efficiently
Success Criteria: < 2 second response time, 100% delivery rate

Test 3: Bounce Handling

Description: Verify bounce detection and suppression
Setup:
  - Email address: test@testcallme.com (known to bounce)

Test Steps:
  1. Send email to bounce test address
  2. Wait for bounce notification (typically < 30 seconds)
  3. Check bounce webhook received
  4. Query /bounces API for hard bounce
  5. Verify bounce suppressed in system
  6. Attempt resend to same address
  7. Verify no additional bounces

Expected Result: Bounce detected, suppressed, and handled
Success Criteria: Hard bounce notification received, bounce in API results

Test 4: Template Rendering

Description: Verify template variables rendered correctly
Setup:
  - Template created with variables: {{UserName}}, {{Code}}, {{ExpirationTime}}
  - Template validated via /templates/validate

Test Steps:
  1. POST /email/withTemplate with model data
     {
       "TemplateId": 12345,
       "TemplateModel": {
         "UserName": "John Doe",
         "Code": "ABC123",
         "ExpirationTime": "1 hour"
       }
     }
  2. Verify email sent successfully
  3. Retrieve email content
  4. Verify {{UserName}} → "John Doe"
  5. Verify {{Code}} → "ABC123"
  6. Verify {{ExpirationTime}} → "1 hour"
  7. Verify HTML properly formatted

Expected Result: Template variables properly substituted
Success Criteria: All variables correctly rendered, no placeholder artifacts

Test 5: Click Tracking

Description: Verify link click tracking functionality
Setup:
  - Email template with trackable link
  - TrackLinks enabled in email config
  - Webhook configured for click events

Test Steps:
  1. Send email with TrackLinks: "HtmlAndText"
  2. Receive email with tracked link
  3. Click tracked link
  4. Verify redirect to original URL
  5. Wait for click webhook (typically < 1 second)
  6. Verify click webhook received with:
     - OriginalLink matches sent link
     - ClickedAt timestamp
     - Client information (Gmail, Outlook, etc)
  7. Query analytics for link click count

Expected Result: Click tracked and recorded
Success Criteria: Click webhook received, analytics show 1+ clicks

Test 6: Open Tracking

Description: Verify open tracking with pixel
Setup:
  - Email template with HTML body
  - TrackOpens enabled
  - Webhook configured for open events

Test Steps:
  1. Send email with TrackOpens: true
  2. Email delivered to inbox
  3. Open email (or preview images)
  4. Wait for open webhook (typically < 5 seconds)
  5. Verify webhook contains:
     - RecordType: "Open"
     - Client information
     - UtcOpenedAt timestamp
  6. Query analytics for opens
  7. Open email again
  8. Verify multiple opens tracked

Expected Result: Opens tracked for subsequent email opens
Success Criteria: Open webhook received, multiple opens recorded

Test 7: Webhook Retry & Error Handling

Description: Verify webhook retry mechanism
Setup:
  - Webhook URL configured to initially fail
  - Webhook configured for bounce events

Test Steps:
  1. Configure webhook to return 500 error
  2. Send email that will bounce (bad address)
  3. Wait for first delivery attempt (< 30 sec)
  4. Verify webhook error logged
  5. Monitor webhook retry schedule:
     - 1 minute: Should retry
     - 5 minutes: Should retry
     - 10 minutes: Should retry
  6. Update webhook to return 200 OK
  7. Verify webhook succeeds on next retry
  8. Verify bounce processed correctly

Expected Result: Webhooks retry with exponential backoff
Success Criteria: Webhook retried at least 3 times, eventually succeeds

Test 8: Message Stream Separation

Description: Verify transactional and broadcast streams are separate
Setup:
  - Two Postmark servers/streams configured
  - Transactional stream for password resets
  - Broadcast stream for newsletters

Test Steps:
  1. Send password reset via transactional stream
  2. Send newsletter via broadcast stream
  3. Verify both emails delivered
  4. Check bounce handling:
     - Hard bounce in transactional → suppress password resets
     - Hard bounce in broadcast → suppress newsletters only
  5. Verify sender reputations independent
  6. Send large volume to broadcast stream
  7. Verify transactional delivery unaffected
  8. Monitor separate analytics for each stream

Expected Result: Streams operate independently
Success Criteria: Bounces isolated per stream, reputation independent

Test 9: Authentication & Failure Modes

Description: Verify proper error handling
Setup:
  - Invalid API token
  - Unverified sender address
  - Exceeded rate limits

Test Steps:
  1. Send with invalid X-Postmark-Server-Token
     Expected: 401 Unauthorized
  2. Send from unverified email address
     Expected: 422 Invalid sender
  3. Submit malformed JSON
     Expected: 400 Bad Request
  4. Submit email without From
     Expected: 400 Bad Request missing field
  5. Submit 51 recipients (max 50)
     Expected: 400 Request too large

Expected Result: Appropriate errors returned
Success Criteria: All error codes correct, error messages clear

Test 10: Spam Complaint Handling

Description: Verify spam complaint suppression
Setup:
  - Email sent to recipient
  - Recipient can mark as spam
  - Webhook configured for spam complaints

Test Steps:
  1. Send email to test account
  2. Mark email as spam in email client
  3. ISP reports spam complaint to Postmark (may take 1-3 days)
  4. Monitor for spam complaint webhook
  5. Verify webhook received with:
     - RecordType: "SpamComplaint"
     - Email address
     - SpamComplainedAt timestamp
  6. Verify address suppressed in system
  7. Attempt to send to same address
  8. Verify hard bounce or suppression
  9. Check bounce list via API

Expected Result: Spam complaints suppressed
Success Criteria: Address removed from future sends, webhook received

Integration Complexity Assessment

Complexity Score: 5/10

Factors Increasing Complexity:

  • Webhook signature validation (medium)
  • DKIM/SPF/DMARC setup (medium, especially for non-technical teams)
  • Template management & variable handling (low-medium)
  • Bounce list synchronization (low-medium)
  • Error handling & retry logic (low)

Factors Decreasing Complexity:

  • Simple, well-documented REST API
  • Clear authentication model
  • No SDK required (though available)
  • Excellent onboarding documentation
  • Pre-built templates available
  • No complex configuration beyond DNS

Comparative Complexity:

  • AWS SES: 7/10 (requires SNS, SQS, Lambda setup)
  • SendGrid: 6/10 (more features, more configuration)
  • Mailgun: 5/10 (comparable to Postmark)
  • Simple SMTP: 2/10 (less reliability, no tracking)

Cost Analysis

10,000 Emails/Month (Small Application)

Postmark: Basic plan $15/month
Cost per email: $0.0015
Competitor comparison:
- AWS SES: $1 (negligible, but setup overhead)
- SendGrid: $19.95/month
- Mailgun: $35/month

Winner: Postmark
Best for: Startups, small applications

100,000 Emails/Month (Growing Application)

Postmark: Basic plan $15 + overage
  40,000 overage × $1.80/1000 = $72
  Total: $87/month = $0.00087 per email

AWS SES: 100,000 × $0.10/1000 = $10 + infrastructure
  Actual cost with monitoring: $50-100/month

SendGrid: Essentials tier $19.95/month (50K included)
  50,000 overage × $0.15/1000 = $7.50
  Total: $27.45/month = $0.000275 per email

Winner: AWS SES (raw price), Postmark (value + support)
Best for: Applications needing cost vs. simplicity balance

1,000,000 Emails/Month (Enterprise)

Postmark: Enterprise plan needed
  Estimated: $500-1500/month depending on terms
  Cost per email: $0.0005-0.0015

AWS SES: $100 + infrastructure
  With monitoring, automation, etc: $500-1000/month
  Cost per email: $0.0001-0.001

SendGrid: Pro plan + overage
  $89.95 + significant overage fees
  Estimated: $500+/month

Winner: AWS SES (cost), Postmark (reliability)
Best for: Large-scale transactional systems

Production Deployment Recommendations

Deployment Phase 1: Setup (Week 1)

1. Create Postmark account and servers
2. Create all required sender signatures
3. Configure DKIM/SPF/DMARC
4. Create and validate email templates
5. Set up webhook endpoints
6. Test in staging environment
7. Create runbooks and documentation

Deployment Phase 2: Rollout (Week 2-3)

1. Enable for non-critical transactional emails (low volume)
2. Monitor delivery rates and bounce handling
3. Gradually increase email volume
4. Monitor bounce list growth
5. Tune bounce handling automation
6. Enable click/open tracking (measure engagement)

Deployment Phase 3: Production (Week 4+)

1. Enable for all transactional emails
2. Set up production monitoring and alerts
3. Configure PagerDuty/on-call integration
4. Create incident response procedures
5. Regular health checks (weekly)
6. Monitor cost per email vs. projections
7. Review analytics monthly

Migration from Existing Provider

From AWS SES

Advantages of Postmark:

  • Simpler API (no SNS/SQS needed)
  • Included support
  • Faster average delivery
  • Better bounce handling

Migration Steps:

  1. Set up Postmark sender signatures
  2. Update environment variables
  3. Replace SES API calls with Postmark equivalents
  4. Migrate existing bounce lists to Postmark suppressions
  5. Update webhooks to Postmark endpoints
  6. Dual-send for 1-2 weeks (both SES and Postmark)
  7. Monitor deliverability comparison
  8. Cut over completely
  9. Retain SES for in-flight emails only

From SendGrid

Advantages of Postmark:

  • Simpler, focused feature set
  • Lower costs for transactional-only
  • Better support at entry level

Migration Steps:

  1. Create Postmark sender signatures
  2. Port templates from SendGrid to Postmark
  3. Update API integration
  4. Map SendGrid dynamic_template_data to Postmark TemplateModel
  5. Update webhook handling
  6. Test email rendering (different engines)
  7. Dual-send for validation
  8. Gradual cutover by email type
  9. Monitor for differences

Troubleshooting Guide

Issue: "Invalid email address specified"

Causes:

  • Sender signature not confirmed
  • From address not added as sender signature

Solution:

  • Add sender signature: Settings → Sender Signatures
  • Confirm email by clicking verification link
  • Wait up to 10 minutes for verification processing

Issue: High Bounce Rate (> 5%)

Causes:

  • Stale email list
  • Incorrect email validation
  • ISP blocks (reputation issue)

Solutions:

  • Review bounce list (API: GET /bounces)
  • Implement double opt-in for new emails
  • Check ISP reputation (MXToolbox)
  • Verify DKIM/SPF configuration
  • Contact Postmark support if legitimate rate > 5%

Issue: Webhooks Not Received

Causes:

  • Webhook URL not HTTPS
  • Webhook URL has typo
  • Firewall blocking Postmark IPs
  • Webhook handler returning non-200 status

Solutions:

  • Test URL manually: curl https://your-url
  • Check webhook configuration in Postmark UI
  • Review Postmark webhook IPs (whitelist if needed)
  • Ensure handler returns 200 immediately
  • Monitor webhook logs for errors
  • Use RequestBin for testing: https://requestbin.com

Issue: Template Validation Failure

Causes:

  • Invalid Handlebars syntax
  • Variable name mismatch
  • Unmatched braces/quotes

Solution:

  • Use Postmark template validation API
  • Review error message for specific syntax issue
  • Validate template model structure
  • Test with simple template first
  • Use Postmark UI template editor for assistance

Issue: DKIM Verification Not Completing

Causes:

  • DNS record not propagated
  • Incorrect DNS record format
  • TTL caching

Solutions:

  • Verify DNS record published: nslookup postmark._domainkey.example.com
  • Wait 5-60 minutes for propagation
  • Check with MXToolbox DKIM check tool
  • Clear browser cache and retry in Postmark UI
  • Verify record format exactly matches (no extra spaces)

Conclusion

Postmark is an excellent choice for InfraFabric systems requiring reliable, fast transactional email delivery. Its specialization in transactional use cases, transparent performance metrics, and straightforward API make it ideal for applications needing < 1-second email delivery with comprehensive tracking and bounce management.

Key Takeaways:

  • Speed: Industry-leading < 1-second delivery
  • Reliability: Separate infrastructure from broadcast/marketing
  • Cost: $15/month base with reasonable overages
  • Simplicity: Purpose-built API with minimal configuration
  • Support: Included ticket support at all tiers

Recommended Integration Points:

  • Password resets and security alerts (time-critical)
  • Order confirmations and receipts (customer-facing)
  • Invoice delivery and billing notifications
  • Account status changes and warnings
  • Multi-user notifications (via separate broadcast stream)

Next Steps:

  1. Sign up for Postmark developer account (free 100 emails/month)
  2. Configure sender signature with DKIM
  3. Create test templates
  4. Implement API integration in non-production environment
  5. Run through all 10 test scenarios
  6. Configure webhooks and monitoring
  7. Plan production rollout with dual-send validation

Document Version: 1.0 Last Updated: 2025-11-14 Status: Production Ready Maintained By: InfraFabric Integration Team