Running under multi-tenancy
The package works under a tenancy layer, but two of its defaults assume a single-tenant application. Neither fails at boot; both fail on the first real request, and one of them fails in a way that reads like a different problem entirely.
Re-define the rate limiters after swapping the cache
Laravel keeps named rate limiters inside the Illuminate\Cache\RateLimiter singleton, and
that singleton captures its cache repository when it is first resolved — before any tenant is
identified. If your tenancy layer binds a cache per tenant, the limiter never learns about it
and every tenant shares one set of buckets.
The usual cure is to discard the singleton so it is rebuilt against the current cache. That works, and it takes every named limiter with it — yours and this package's. You re-register your own. Register the package's in the same place:
use EmailMagicLink\Support\RateLimits;
// wherever you re-register your own limiters after tenancy is initialized
app(RateLimits::class)->define();
That one call registers every limiter the package names. Do not copy the individual
RateLimiter::for() lines: the limits themselves would still come from the package, but the
wiring would be yours to maintain, and a limiter added in a later release would go missing
without a word. You would find out when a route answered 500:
MissingRateLimiterException: Rate limiter [email-magic-link:consume] is not defined
The call is safe to make more than once — defining a limiter again replaces it.
Give the routes the tenancy middleware
The shipped default is ['web']:
'routes' => [
'middleware' => ['web'],
],
Under tenancy that is not enough. The controllers resolve against whichever connection is
current, and with only web in the stack that is the central one — where this package's
tables do not live. The query succeeds and finds nothing.
This is the failure worth knowing about in advance, because the screen does not describe it: a token that is absent and a token that has expired both render as an invalid link. Nothing is logged as an error, because as far as the package can tell, nothing went wrong.
Add your tenancy middleware to the group:
'routes' => [
'middleware' => ['web', InitializeTenancyByDomain::class, PreventAccessFromCentralDomains::class],
],
The exact classes depend on your tenancy package and on how tenants are identified; what matters is that the tenant connection is active by the time the route runs. If you serve the sign-in screens from the central domain on purpose, leave the default and make sure the package's migrations ran on the central connection instead — the requirement is that the tables and the request agree on a connection, not that either one is tenant-scoped.
Purging under tenancy
The prune.schedule switch registers email-magic-link:purge as a plain subprocess with no
tenant context, so it runs against the central connection. Leave the switch off and schedule
the command through your tenancy runner instead — for example, with Tenancy for Laravel:
Schedule::command('tenants:run email-magic-link:purge')->daily();
The exact command depends on your tenancy package; what matters is that the purge runs once per tenant connection. With the switch on and the tables only on tenant databases, the central run fails every night; with the tables also on the central database, it reports success and purges nothing while every tenant table grows.