jeffersongoncalves / laravel-postmark
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!
Requires
- php: ^8.2
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.21
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel Postmark
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'sErrorCodeand error body) on any non-2xx response - Throws
InvalidArgumentExceptionbefore 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.
