henrikbjorn/stampie
Stampie is a PHP library for sending email through API-based providers such as Mailgun, Mandrill, Postmark, and SendGrid. It provides a consistent mailer and message API, making it easy to switch transactional email services without rewriting your sending code.
stampie/stampie conventions, so first verify composer.json (name, autoload, PHP/Guzzle constraints) before running:composer require henrikbjorn/stampie
README.md for the supported providers and minimal examples.src/Stampie/Mailer/ for concrete provider mailers (Mailgun, SendGrid, Postmark, Mandrill, etc.).src/Stampie/Message/ for the message object and attachment handling.src/Stampie/Adapter/ for the HTTP transport (historically Guzzle/Buzz).tests/ for exact constructor signatures and expected request payloads.use Stampie\Adapter\Guzzle;
use Stampie\Mailer\Mailgun;
use Stampie\Message\Message;
$mailer = new Mailgun(
new Guzzle(new \GuzzleHttp\Client()),
config('services.mailgun.domain'),
config('services.mailgun.secret')
);
$message = (new Message())
->setSender('[email protected]', 'My App')
->setTo('[email protected]')
->setSubject('Welcome')
->setText('Welcome!')
->setHtml('<h1>Welcome!</h1>');
$mailer->send($message);
// AppServiceProvider::register()
$this->app->singleton(\Stampie\Mailer::class, function () {
return new \Stampie\Mailer\Mailgun(
new \Stampie\Adapter\Guzzle(new \GuzzleHttp\Client()),
config('services.mailgun.domain'),
config('services.mailgun.secret')
);
});
config/services.php and .env, never inline secrets:// config/services.php
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
],
final class TransactionalMailer
{
public function __construct(private \Stampie\Mailer $mailer) {}
public function sendWelcome(User $user): void
{
$message = (new \Stampie\Message\Message())
->setSender(config('mail.from.address'), config('mail.from.name'))
->setTo($user->email, $user->name)
->setSubject('Welcome')
->setText(view('mail.welcome-text', compact('user'))->render())
->setHtml(view('mail.welcome', compact('user'))->render());
$this->mailer->send($message);
}
}
From, Reply-To, common headers, provider tags). Laravel Mailables are a good model, but do not try to reproduce the whole Laravel mail stack unless needed.final class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public function __construct(public User $user) {}
public function handle(TransactionalMailer $mailer): void
{
$mailer->sendWelcome($this->user);
}
}
Use provider-specific features deliberately:
Message is enough. Add provider-specific payloads only when a feature requires it.For testing, inject a fake adapter rather than hitting the provider. If the adapter is Guzzle-based, use Guzzle’s MockHandler; otherwise implement the package’s adapter interface and return a canned response. Assert on the captured request body, not just “send was called.”
Log the provider response/message ID after a successful send. That ID is usually what provider support needs when debugging delivery.
If migrating from Laravel’s native mailer, put Stampie behind an interface and keep call sites unchanged. This makes it possible to revert to Laravel mail or Symfony Mailer later without rewriting application code.
composer.json.henrikbjorn/stampie vs stampie/stampie); do not assume class names from an old blog post.send() throws on a 4xx/5xx. Inspect the response status and body, and throw your own exception so queue retries work correctly.text and html parts. Some providers reject messages without a text fallback; some spam filters score HTML-only mail poorly.To addresses; misuse can get an account throttled.env() calls. Keep all provider credentials in config files, and clear/rebuild config after changes.Bcc and provider-specific headers. Some providers strip or rewrite headers; verify in the provider dashboard.How can I help you explore Laravel packages today?