Search by

jeffersongoncalves / laravel-postmark

jeffersongoncalves

PHP/Laravel client for the Postmark REST API: transactional email, templates, bounces, messages, stats, server and suppressions.

Package info

github.com/jeffersongoncalves/laravel-postmark

pkg:composer/jeffersongoncalves/laravel-postmark

Fund package maintenance!

jeffersongoncalves

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.0.0 2026-09-08 00:38 UTC

This package is auto-updated.

Last update: 2026-09-08 00:41:53 UTC


README

Laravel Postmark

Laravel Postmark

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads License

A PHP/Laravel client for the Postmark REST API. Covers transactional email (single, templated and batch), templates, bounces, message search, outbound statistics, server details and suppressions through a simple, typed API built on Laravel's Http client.

Features

  • Transactional email: send a single message, a templated message, or a batch of either
  • Templates: list, get, create, update, delete
  • Bounces: list with filters, get, raw dump, reactivate, delivery stats
  • Messages: search outbound and inbound, get message details
  • Stats: outbound overview, sends, bounces, opens, clicks, spam
  • Server: get the authenticated server's details
  • Suppressions: dump, create and delete per message stream
  • Throws PostmarkException (carrying Postmark's ErrorCode and error body) on any non-2xx response
  • Throws InvalidArgumentException before hitting the API when a required field is missing

Installation

You can install the package via composer:

composer require jeffersongoncalves/laravel-postmark

Publish the config file:

php artisan vendor:publish --tag=postmark-config

Set your Postmark server token in .env:

POSTMARK_TOKEN=your-server-token

Find it under Servers > (your server) > API Tokens in your Postmark account.

Configuration

// config/postmark.php
return [
    'api_url' => env('POSTMARK_API_URL', 'https://api.postmarkapp.com'),
    'token' => env('POSTMARK_TOKEN', ''),
    'message_stream' => env('POSTMARK_MESSAGE_STREAM', 'outbound'),
    'default_count' => env('POSTMARK_DEFAULT_COUNT', 50),
];

message_stream is applied to every send and suppression call that does not pass its own, and default_count is the page size used by the list endpoints.

Usage

The package is resolved via the Postmark facade or by injecting JeffersonGoncalves\Postmark\Postmark. Each API group is exposed as a method returning a dedicated resource class; sending and the server endpoint live directly on the manager.

Sending email

use JeffersonGoncalves\Postmark\Facades\Postmark;

Postmark::sendEmail(
    from: 'sender@example.com',
    to: 'recipient@example.com',
    subject: 'Welcome aboard',
    htmlBody: '<p>Hello!</p>',
);

// Several recipients (Postmark accepts up to 50 per message):
Postmark::sendEmail('sender@example.com', ['a@example.com', 'b@example.com'], 'Hi', textBody: 'Hi');

// Any other Postmark field goes in $options:
Postmark::sendEmail(
    from: 'sender@example.com',
    to: 'recipient@example.com',
    subject: 'Welcome aboard',
    htmlBody: '<p>Hello!</p>',
    options: [
        'Cc' => 'cc@example.com',
        'ReplyTo' => 'reply@example.com',
        'Tag' => 'welcome',
        'TrackOpens' => true,
        'TrackLinks' => 'HtmlAndText',
        'MessageStream' => 'broadcast',
    ],
);

Sending with a template

An integer is sent as TemplateId, a string as TemplateAlias:

Postmark::sendEmailWithTemplate('sender@example.com', 'recipient@example.com', 'welcome', [
    'name' => 'Ada',
    'product_url' => 'https://example.com',
]);

Postmark::sendEmailWithTemplate('sender@example.com', 'recipient@example.com', 1234567);

Sending a batch

Batches take raw Postmark message payloads; the configured message stream is applied to any message that does not set its own.

Postmark::sendBatch([
    ['From' => 'sender@example.com', 'To' => 'a@example.com', 'Subject' => 'Hi', 'TextBody' => 'Hi'],
    ['From' => 'sender@example.com', 'To' => 'b@example.com', 'Subject' => 'Hi', 'TextBody' => 'Hi'],
]);

Postmark::sendBatchWithTemplates([
    ['From' => 'sender@example.com', 'To' => 'a@example.com', 'TemplateAlias' => 'welcome', 'TemplateModel' => ['name' => 'Ada']],
]);

Templates

Postmark::templates()->list();
Postmark::templates()->list(count: 10, offset: 0, type: 'Standard');
Postmark::templates()->get('welcome');
Postmark::templates()->create('Welcome', 'Welcome aboard', [
    'Alias' => 'welcome',
    'HtmlBody' => '<p>Hello {{name}}</p>',
    'TextBody' => 'Hello {{name}}',
]);
Postmark::templates()->update('welcome', ['Subject' => 'Welcome!']);
Postmark::templates()->delete('welcome');

Bounces

Postmark::bounces()->list(['type' => 'HardBounce', 'inactive' => true]);
Postmark::bounces()->get(692560173);
Postmark::bounces()->dump(692560173);   // raw bounce source
Postmark::bounces()->activate(692560173); // reactivate the address
Postmark::bounces()->deliveryStats();

Messages

Postmark::messages()->outbound(['tag' => 'welcome', 'status' => 'sent']);
Postmark::messages()->inbound(['recipient' => 'inbox@example.com']);
Postmark::messages()->get('0a129aee-e1cd-480d-b08d-4f48548ff48d');
Postmark::messages()->inboundDetails('e2ecbbfc-fe12-463d-b933-9fe22915106d');

Stats

Postmark::stats()->overview();
Postmark::stats()->sends(tag: 'welcome', fromDate: '2026-01-01', toDate: '2026-01-31');
Postmark::stats()->bounces();
Postmark::stats()->opens();
Postmark::stats()->clicks();
Postmark::stats()->spam();

Server

Postmark::server();

Suppressions

Postmark::suppressions()->list();                    // configured stream
Postmark::suppressions()->list('broadcast');
Postmark::suppressions()->create('bounced@example.com');
Postmark::suppressions()->create(['a@example.com', 'b@example.com'], 'broadcast');
Postmark::suppressions()->delete('a@example.com');

Error handling

use JeffersonGoncalves\Postmark\Exceptions\PostmarkException;

try {
    Postmark::sendEmail('sender@example.com', 'recipient@example.com', 'Hi', textBody: 'Hi');
} catch (PostmarkException $e) {
    $e->getMessage();  // Postmark's "Message"
    $e->getCode();     // Postmark's "ErrorCode", or the HTTP status when absent
    $e->errorBody();   // the full decoded error body
}

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.