padosoft / laravel-invitations
Enterprise invite-by-code, referral, rewards, waitlist & anti-abuse system for Laravel — multi-tenant, concurrency-safe, idempotent, GDPR-ready.
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.92
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/mcp: ^0.7|^0.8
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.5|^12.0
- spatie/laravel-permission: ^6.0
Suggests
- laravel/fortify: Integrate the invite-gate with Fortify's registration pipeline.
- laravel/mcp: Register the bundled MCP tools (InviteValidateCode, InviteGenerateCodes, InviteMetrics) on your MCP server.
- spatie/laravel-permission: Enables the default SpatiePermissionProvisioner to grant roles on redemption.
This package is auto-updated.
Last update: 2026-06-23 16:01:11 UTC
README
Laravel Invitations
The enterprise invite‑by‑code, referral, rewards, waitlist & anti‑abuse suite for Laravel.
Multi‑tenant · concurrency‑safe · idempotent redemption · GDPR‑ready · tri‑surface (PHP + HTTP API + MCP)
⚠️ Active development toward
v1.0.0. The engine is production‑proven (extracted from a shipping app) and fully tested; the public API may still shift before thev1.0.0tag.
🚀 AI vibe‑coding pack included
This repo ships a complete AI pair‑programming kit: CLAUDE.md (engineering invariants
- quality gates),
AGENTS.md, and the design/roadmap docs underdocs/. Point Claude Code, Cursor, or Copilot at the repo and they inherit the package's rules (atomic redemption, tenant scoping, fail‑open fraud, GRANT‑never‑REVOKE) automatically.
Why this package
Every Laravel invite/referral package on the market stops at "generate a code, mark it used." None of them solve the problems that actually bite in production:
- They over‑redeem under load. The popular packages increment a use‑counter with a check‑then‑write and no lock — two concurrent redemptions both pass the "1 seat left" check. That's a free‑code / over‑capacity bug at best.
- They're single‑tenant. Codes are globally unique, so two customers can never share an intuitive code, and rows leak across tenant boundaries.
- They store invitee emails forever with no erasure path — a GDPR liability.
- They have no events, no fraud controls, no analytics, and no API/MCP surface.
padosoft/laravel-invitations is built the other way around: correctness, multi‑tenancy, privacy and
observability first.
✨ Highlights
- 🎟️ Invite codes — random, vanity, and cryptographically signed (Crockford Base32, no confusable characters), with expiry, max‑uses and per‑user limits.
- ⚛️ Atomic, idempotent, concurrency‑safe redemption — a single conditional
UPDATE … WHERE current_uses < max_usesthat flips state in the same statement, backed by aUNIQUE(code_id, redeemer_id)index.current_usescan never exceedmax_uses; a replay is a no‑op, never a double‑grant — even under a thundering herd. - 🏢 Multi‑tenant by design — every table is tenant‑scoped; two tenants can share the same human code. Single‑tenant apps get a zero‑config default.
- 🎁 Referral graph + double‑sided rewards with a DB‑backed idempotency key (no double‑grants).
- 📨 Email invitations — idempotent send/accept lifecycle, "who accepted vs. who didn't".
- 🛡️ Fail‑open anti‑abuse — weighted velocity / disposable‑email / honeypot / blacklist scoring
that surfaces a generic
rate_limited(never a probing oracle) and stores HMAC‑hashed PII only. A detector fault never blocks a real user; seat safety is the atomic claim's job. - 🔑 Per‑invite entitlement grants — an invite can grant a role and project access on redemption, across one or several tenants. GRANT‑never‑REVOKE: it only ever raises access.
- 📈 Virality analytics — K‑factor, acceptance / conversion rates, time‑to‑redeem percentiles, reconciled against the canonical rows (not a drifting rollup).
- 🔔 Events on every lifecycle transition.
- 🔒 GDPR — in‑place PII anonymization that preserves aggregates + a scheduled prune command.
- 🧩 Tri‑surface — the same core is reachable from PHP (services + Artisan), a REST API (RBAC‑gated, publishable routes), and MCP tools.
- 🤝 Vendor‑neutral — works on plain Fortify/Breeze;
spatie/laravel-permission,laravel/fortifyandlaravel/mcpare optional, first‑class integrations.
How it compares
| Capability | laravel‑invitations | doorman | mateusjunges/invite‑codes | pdazcom/referrals | taldres/waitlist |
|---|---|---|---|---|---|
| Invite codes (max‑uses) | ✅ | ✅ | ✅ | — | — |
| Concurrency‑safe redemption | ✅ | ❌ | ❌ | — | — |
| Idempotent replay | ✅ | ❌ | ❌ | ⚠️ | ⚠️ |
| Multi‑tenant scoping | ✅ | ❌ | ❌ | ❌ | ❌ |
| Vanity / signed codes | ✅ | ❌ | ⚠️ | — | ⚠️ |
| Email invitations | ✅ | ✅ | ⚠️ | ❌ | ⚠️ |
| Referral graph + double‑sided rewards | ✅ | ❌ | ❌ | ⚠️ referrer‑only | ❌ |
| Anti‑abuse / fraud | ✅ | ❌ | ❌ | ⚠️ self‑ref only | ❌ |
| Per‑invite role/entitlement grant | ✅ | ❌ | ❌ | ❌ | ❌ |
| Virality analytics (K‑factor) | ✅ | ❌ | ❌ | ❌ | ❌ |
| GDPR erasure | ✅ | ❌ | ❌ | ❌ | ✅ |
| Events / hooks | ✅ | ❌ | ✅ | ✅ | ✅ |
| HTTP API + MCP surface | ✅ | ❌ | ❌ | ❌ | ❌ |
Requirements
- PHP
^8.3 - Laravel
^12.0 | ^13.0
Installation
composer require padosoft/laravel-invitations php artisan migrate
Make your user model invitation‑aware:
use Illuminate\Foundation\Auth\User as Authenticatable; use Padosoft\Invitations\Concerns\InteractsWithInvitations; use Padosoft\Invitations\Contracts\InvitedAccount; class User extends Authenticatable implements InvitedAccount { use InteractsWithInvitations; // reads `email` + auth guard for the engine }
Publish the config (optional):
php artisan vendor:publish --tag=invitations-config
Quick start
Generate codes (PHP):
use Padosoft\Invitations\Services\CodeGenerator; $code = app(CodeGenerator::class)->generateRandom(['max_uses' => 100]); $batch = app(CodeGenerator::class)->generateBatch(500); // 500 distinct codes
Redeem a code — atomic, idempotent, fraud‑gated:
use Padosoft\Invitations\Services\RedemptionService; $result = app(RedemptionService::class)->redeem($rawCode, $user, [ 'ip' => $request->ip(), 'fingerprint' => $request->header('X-Device'), ]); if ($result->ok) { // $result->already === true on an idempotent replay (no second grant) // $result->redemption, $result->referral } else { // $result->error: invalid | expired | exhausted | revoked | ineligible | rate_limited }
Over the REST API (routes auto‑register; attach your own auth/RBAC via config):
POST /api/invitations/redeem { "code": "Q7K92MNP" } POST /api/invitations/validate { "code": "Q7K92MNP" } # advisory, writes nothing GET /api/admin/invitations/metrics POST /api/admin/invitations/codes { "count": 50, "max_uses": 1 }
Over MCP — register the bundled tools on your server:
// app/Mcp/Servers/YourServer.php public array $tools = [ \Padosoft\Invitations\Mcp\Tools\InviteValidateCodeTool::class, \Padosoft\Invitations\Mcp\Tools\InviteGenerateCodesTool::class, \Padosoft\Invitations\Mcp\Tools\InviteMetricsTool::class, ];
Architecture
flowchart LR
Caller["PHP · HTTP API · MCP · CLI"] --> RS[RedemptionService]
RS -->|validate| CV[CodeValidator]
RS -->|fail-open gate| FD[FraudDetector]
RS -->|atomic UPDATE + UNIQUE| DB[(invite_codes / invite_redemptions)]
RS -->|attribute| REF[ReferralService] --> RE[RewardEngine]
RS -->|GRANT-never-REVOKE| PROV[Provisioner*]
RS -->|fire once| EV([CodeRedeemed event])
MS[MetricsService] --> DB
Loading
* Provisioners are pluggable: the SpatiePermissionProvisioner (role grant) ships by default; a host
adds its own under the invitations.provisioners tag.
Host integration seams
The engine never hard‑codes your app. Three small seams keep it vendor‑neutral:
| Seam | Default | Override when… |
|---|---|---|
Contracts\TenantResolver |
single‑tenant ('default') |
you're multi‑tenant — bind your own resolver |
Contracts\Provisioner (tag invitations.provisioners) |
SpatiePermissionProvisioner (role) |
you grant more on redemption (e.g. team/project membership) |
Contracts\InvitedAccount |
InteractsWithInvitations trait |
your user model stores email differently |
// A multi-tenant host, in a service provider: $this->app->bind(\Padosoft\Invitations\Contracts\TenantResolver::class, MyTenantResolver::class); $this->app->tag([MyProjectMembershipProvisioner::class], 'invitations.provisioners');
Events
CodeRedeemed (fired once, on a fresh claim — never on an idempotent replay), InvitationSent,
InvitationAccepted. Listen to grant perks, send a welcome, or update your own projections.
GDPR
PII (ip / fingerprint / recipient) is stored hashed or anonymizable. The scheduled sweep anonymizes
rows past the retention window in place — current_uses, funnel counts and K‑factor are untouched:
php artisan invite:prune-pii --days=90
Configuration
All knobs live in config/invitations.php and are env‑overridable — code alphabet/length, signing key,
PII retention, anti‑abuse thresholds/velocity/blocklists, and the route prefix + per‑surface middleware
(attach your RBAC gate to invitations.routes.admin_middleware).
Web admin panel
padosoft/laravel-invitations-admin ships a
polished React + Tailwind admin SPA over this package's API — a virality dashboard plus campaigns,
codes, invitations (who accepted vs. who didn't), referral graph, reward ledger, waitlist and
anti‑abuse review. Default‑OFF, host‑gated, prebuilt assets (no JS toolchain to install). For apps that
already run their own React SPA, the screens can be adapted natively instead of cross‑mounting.
Testing
composer test # PHPUnit (Testbench) composer analyse # PHPStan composer check # format + analyse + test
