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.
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:
composer require <vendor>/<package>:^0.4.1
php artisan vendor:publish --tag=<package>-config
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.register() method:
public function register(): void
{
$this->app->bind(
\Vendor\Package\Contracts\MailLockdown::class,
\App\Mail\Lockdown\EnvironmentLockdown::class,
);
}
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.
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.
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.register(), not boot(). The contracts resolve early, and a boot()-time binding will silently lose to the default binding already resolved by a listener.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.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.How can I help you explore Laravel packages today?