Multi-use links
A magic link is single-use by default. To hand out a link that may be redeemed a bounded
number of times — a shared invite, a multi-device sign-in — pass maxUses:
$link = $issuer->issueLink($user, maxUses: 3); // redeemable three times
Why concurrent redemptions cannot exceed the limit
Each redemption decrements a remaining-uses counter in the same conditional UPDATE that
consumes the token, so concurrent redemptions can never exceed the limit — the count is
checked and decremented atomically, never read-then-written.
The behavior is proven against real PostgreSQL and MySQL row locking, not only SQLite:
consumed_at is set before the decrement in the SET list so MySQL agrees with PostgreSQL
and SQLite about what the row looked like when the condition was evaluated.
When the uses run out
Once exhausted the link behaves exactly like a spent or expired one — the same generic, enumeration-resistant failure, and the same invalid-link response you configured. A reader can never tell an exhausted link from an unknown one.
The default for every link
Set the default for every link with the max_uses config key:
'max_uses' => 1,
One-time codes are always single-use regardless of this value.
The sign-in mail says how often a multi-use link works ("can be used 3 times"), so its
reader does not treat the mail as spent after the first sign-in. If you publish the
translations, the line is mail_link_expiry_reusable, with :minutes and :uses.