Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Stampie Bundle Laravel Package

henrikbjorn/stampie-bundle

Symfony bundle that integrates Stampie, providing a configured mailer service for sending transactional email through providers like SendGrid, Mailgun, Postmark, and Mandrill. Configure API credentials in Symfony and inject the mailer where needed.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle is a thin Symfony DI wrapper around stampie/stampie. The first place to look is the bundle README for the stampie: config tree, then the Stampie\Mailer\* classes in the library for the provider-specific options each mailer accepts.

Version note (v2.1.1): this release fixes Symfony 5 compatibility. If you are on Symfony 5, pin at least this version — earlier 2.1.x releases will fail to compile the container:

composer require henrikbjorn/stampie-bundle:^2.1.1

The package is low-activity rather than dead: fixes still land, but slowly, and only for the versions someone has sponsored/PR'd. Treat the supported-version window as narrow and verify it against your own Symfony version before committing.

  1. Register the bundle:
// config/bundles.php
return [
    // ...
    Stampie\StampieBundle::class => ['all' => true],
];
  1. Configure at least one named mailer:
# config/packages/stampie.yaml
stampie:
    mailers:
        default:
            type: mailgun
            options:
                api_key: '%env(MAILGUN_API_KEY)%'
                domain: '%env(MAILGUN_DOMAIN)%'
  1. Send your first transactional message through the stampie service (the default mailer alias):
use Stampie\Message;

$message = new Message();
$message->setFrom('[email protected]');
$message->setTo($user->getEmail());
$message->setSubject('Reset your password');
$message->setText('Open this link to reset your password: ...');
$message->setHtml('<p>Open <a href="...">this link</a> to reset your password.</p>');

$this->get('stampie')->send($message);

$providerMessageId = $message->getMessageId(); // store for support/debugging

The canonical first use case is exactly this: password resets, receipts, and notifications where you want a provider HTTP API instead of SMTP.

Implementation Patterns

Alias the mailer for autowiring. The bundle exposes services as stampie.mailer.<name>; stampie points at the default. Add an explicit alias so type-hinting works regardless of bundle version:

services:
    Stampie\Mailer:
        alias: stampie.mailer.default
final class PasswordResetNotifier
{
    public function __construct(private \Stampie\Mailer $mailer) {}

    public function notify(User $user, string $token): void
    {
        $message = new \Stampie\Message();
        $message->setFrom('[email protected]');
        $message->setTo($user->getEmail());
        $message->setSubject('Reset your password');
        $message->setText(sprintf('Reset link: https://example.com/reset/%s', $token));

        $this->mailer->send($message);
    }
}

Centralize message construction. Keep a small MessageFactory or application-level Mailer service that knows your domain objects and template rendering. Controllers and command handlers should not repeat setFrom/setHtml logic.

Use multiple mailers for routing. Define separate mailers for transactional vs. marketing traffic (different providers, API keys, or subaccounts):

stampie:
    mailers:
        default:
            type: postmark
            options: { api_key: '%env(POSTMARK_KEY)%' }
        marketing:
            type: sendgrid
            options: { api_key: '%env(SENDGRID_KEY)%' }

Retrieve them with stampie.mailer.marketing, or build a small registry/locator in your app if you need runtime selection by tenant or feature.

Queue via Symfony Messenger. Stampie uses synchronous HTTP calls, so sending in a web request adds provider latency. Dispatch a Messenger message containing scalar data (recipient, template name, parameters), and let the handler build and send a fresh Stampie\Message. Do not serialize Stampie\Message itself — attachments and internal state do not queue cleanly.

Decorate/wrap for logging and metrics. A wrapper service around Stampie\Mailer is simpler and safer than decorating the abstract mailer service directly:

final class LoggingMailer
{
    public function __construct(
        private \Stampie\Mailer $inner,
        private \Psr\Log\LoggerInterface $logger,
    ) {}

    public function send(\Stampie\MessageInterface $message): void
    {
        try {
            $this->inner->send($message);
            $this->logger->info('Email sent', ['id' => $message->getMessageId()]);
        } catch (\Stampie\Exception\ApiException $e) {
            $this->logger->error('Email failed', ['exception' => $e]);
            throw $e;
        }
    }
}

Retry carefully. Catch Stampie\Exception\ApiException, inspect the status code and provider response, and only retry transport/5xx-style failures. Retrying after a 2xx response is not possible to detect reliably, so avoid blind retries that can duplicate emails. Symfony's Retry component or Messenger's retry strategy both work; log the provider message ID when available.

Testing. In config/services_test.yaml, alias Stampie\Mailer to an in-memory fake (a subclass of Stampie\Mailer implementing doSend, or a test double) and assert on recorded messages. Alternatively, if your bundle version exposes Stampie's logger mailer (type: logger), use it in dev/test and assert on logs.

Pin and watch the version. Because fixes arrive as point releases rather than a maintained LTS line, pin an explicit constraint (^2.1.1) and read the changelog before jumping a minor. A container that compiles today can break on an unrelated composer update if you allow *.

Gotchas and Tips

  • Low-maintenance, not abandoned — but still a risk. v2.1.1 (Symfony 5 DI compatibility) shows fixes do land, so the "no compatibility fixes ever" assumption is too strong. The practical risk is unchanged: no security response process, no roadmap, and supported Symfony versions lag well behind current releases. Evaluate Symfony Mailer bridges or direct provider SDKs for new work, and only stay here if you're pinned to an older Symfony or already invested.
  • Symfony 5 works as of 2.1.1; newer majors are unverified. The fix targets Symfony 5 specifically. Symfony 6/7 are not covered by this release — do not read it as broad forward compatibility. If you're above Symfony 5, expect to patch the DI extension yourself or move off the bundle.
  • Upgrade hygiene after bumping to 2.1.1. Clear the container cache after the update (bin/console cache:clear); DI extension changes are compiled into the container, so a stale cache can mask or mimic the very error the patch fixes. Also re-check any services.yaml overrides that touched the bundle's service definitions.
  • Provider API versions matter. Verify that the Stampie\Mailer\<Provider> implementation still targets a live API endpoint; a successful container build does not mean the provider will accept the request. This is unrelated to the Symfony 5 fix and remains the most common silent failure.
  • No Flex recipe. If config/bundles.php registration fails, register manually; if DI compilation fails, check for deprecated service-definition APIs or PHP version mismatch.
  • Keys belong in env/secrets, not YAML. Use %env(...)%. Remember that Symfony compiles config: changing an env var in production generally requires a cache clear unless you use runtime env resolution.
  • Know which mailer stampie points to. The default alias can be driven by a default_mailer-style option depending on version. When in doubt, inject the explicit stampie.mailer.<name> service or define your own alias.
  • from must be verified. Most providers reject unverified senders/domains. The resulting ApiException usually contains the provider's explanation — log the exception message and response body, not just the status code.
  • Recipient privacy. Passing multiple addresses to setTo() puts them all in the To header. Use Bcc or send individual messages when recipients should not see each other.
  • Attachments are read at send time. They need to be readable local paths; remote files must be downloaded first. Large attachments increase memory usage and provider payload limits.
  • Always set both text and HTML. Some clients and spam filters prefer a multipart message; text-only fallbacks improve deliverability.
  • Rate limits are your problem. Sending is synchronous. A 429 blocks the request; implement backoff and queueing rather than retrying tightly in the request cycle.
  • Message IDs are not universal. Not every provider returns one; store it when present, but do not make it a required part of your send flow.
  • Headers, tags, and metadata vary by provider. Use setHeaders(), setTag(), and setMetadata() only after confirming the specific mailer supports them.
  • Extending with a custom provider. Subclass Stampie\Mailer, implement doSend, register it as a service, and wire it through your own application mailer. The bundle's type switch will not know custom types unless you override the DI extension. If you find yourself patching the DI extension for Symfony compatibility, this is also the place to fork.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules