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

Built For Cloud Contracts Laravel Package

artisan-build/built-for-cloud-contracts

Contracts-only package from artisan-build that supplies interfaces and abstractions for cloud-focused Laravel/PHP applications, helping standardize integrations and keep implementations swappable and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

This package gives you a set of contracts (interfaces) for putting outbound mail into a "managed" mode — one place to decide whether a message is allowed to leave the application, who it is allowed to reach, and what happens when it isn't. v0.4.1 adds the lockdown contracts themselves (PR #6), which is the piece that makes the package usable as an enforcement layer rather than just a config convention.

Minimal path to first value:

  1. Install and publish config:
    composer require <vendor>/<package>:^0.4.1
    php artisan vendor:publish --tag=<package>-config
    
  2. Read the Contracts namespace first. That is the actual API surface of this release — everything else (config keys, the service provider defaults) is convenience around it. Expect roughly three roles: a policy/guard that answers "may this message go out?", a recipient resolver that rewrites or allowlists addresses, and a reporter that records what was blocked.
  3. Bind your own implementation in a service provider's register() method:
    public function register(): void
    {
        $this->app->bind(
            \Vendor\Package\Contracts\MailLockdown::class,
            \App\Mail\Lockdown\EnvironmentLockdown::class,
        );
    }
    
  4. First use case: stop staging from emailing real customers. Implement the guard so that in local/staging every recipient is rewritten to a sink inbox, and anything addressed outside an allowlist is blocked and logged. That single use case justifies the package on its own and exercises all three contracts.

If you only ever need "always send to mailpit in local," Laravel's own Mail::alwaysTo() is enough. Reach for this package when the rule is conditional (per tenant, per mailable class, per environment tier) or when you need an audit trail of what was suppressed.

Implementation Patterns

Environment-tiered lockdown. Keep the policy declarative and let the container pick the implementation per environment:

// config/mail-lockdown.php
return [
    'mode' => env('MAIL_LOCKDOWN_MODE', 'passthrough'), // passthrough|redirect|block
    'sink' => env('MAIL_LOCKDOWN_SINK', '[email protected]'),
    'allow' => array_filter(explode(',', env('MAIL_LOCKDOWN_ALLOW', ''))),
];

redirect is the mode you want on staging (mail is delivered, but only to the sink — useful for eyeballing rendered templates). block is for CI and for production-adjacent environments where a stray Mail::to() should throw. passthrough is production.

Enforce at the transport boundary, not at the call site. The durable pattern is to intercept once — via the MessageSending event, a decorating Mailer, or whatever hook the contracts expose — rather than asking every developer to remember a guard call. If the package ships a listener, register it and treat direct Mail::to(...)->send() as covered by default. Audit your codebase for anything that bypasses the mailer and fix those call sites individually.

Per-mailable opt-outs. Some mail genuinely must escape lockdown (internal alerting to PagerDuty, deliverability probes). Prefer an explicit marker over an allowlist entry:

final class PageOnCall extends Mailable implements ExemptFromLockdown
{
    //
}

Marker interfaces survive refactors and are greppable; email-address allowlists rot silently.

Tenant-scoped policy. If lockdown rules differ per tenant, implement the guard as a thin resolver that reads context (tenant, feature flag) and delegates. Keep the resolver free of mail-sending logic so it stays unit-testable without the mailer.

Test the guard, not just the mail. Because Mail::fake() short-circuits before delivery, your lockdown code never runs in a faked test. Write two kinds of tests: faked assertions that the right mailable was queued, and unfaked tests against a real (array or log) transport that assert the guard blocked/redirected correctly. Otherwise the enforcement layer is the least-tested code you own.

Gotchas and Tips

  • Queued mail runs in the worker. Any guard keyed to request context (hostname, authenticated user, middleware-set flags) will evaluate incorrectly — or not at all — when the mailable is queued. Resolve decisions at dispatch time and serialize them onto the job, or make the guard depend only on environment/config, which the worker shares.
  • Notifications go through the same path. The mail notification channel and anything using Mailable under the hood hit the mailer too. Verify third-party packages (billing, auth, error trackers) that call the mailer directly are covered — they are the usual source of a staging email reaching a real customer.
  • Bind in register(), not boot(). The contracts resolve early, and a boot()-time binding will silently lose to the default binding already resolved by a listener.
  • Choose throw-vs-drop deliberately. Silent suppression is right for staging; for local development an exception with the original recipients in the message is far more useful than a log line nobody reads. Make it configurable and default to the loud option outside production.
  • Config caching. php artisan config:cache freezes MAIL_LOCKDOWN_*. If a deploy script sets the mode after the cache is built, you get the wrong mode with no error. Read mode via env() only inside the config file, never in the guard.
  • Mail::fake() hides regressions. See above — it's the single most common reason people believe lockdown is working when it isn't.
  • 0.x contract churn. At v0.4.x the interfaces in Contracts are still moving (this release added a whole set). Pin with ^0.4.1 and re-read the contract diff, not just the changelog, before taking 0.5. If you need stability, define your own narrow interface, implement the package contract in one adapter class, and let that be the only file that changes.
  • Log blocked mail with enough context to reconstruct it — mailable class, intended recipients, guard decision, environment. You will need this the first time someone asks why a customer never got a receipt.
  • Don't let the sink become a silent graveyard. Redirect mode plus a sink inbox nobody opens is functionally identical to dropping mail. Have the sink write to a channel someone actually watches, or set a retention expectation.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource