CommunityCodierung & Entwicklunggithub.com

Taldres/laravel-waitlist

Run the waitlists of several products in one Laravel app with taldres/laravel-waitlist projects: define projects, scope facade calls with project(), resolve the project of HTTP signups, send mail per project, handle requests across all projects, or keep projects in a database for a central waitlist API.

Was ist laravel-waitlist?

laravel-waitlist is a Claude Code agent skill that run the waitlists of several products in one Laravel app with taldres/laravel-waitlist projects: define projects, scope facade calls with project(), resolve the project of HTTP signups, send mail per project, handle requests across all projects, or keep projects in a database for a central waitlist API.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Taldres/laravel-waitlist/tree/HEAD/resources/boost/skills/laravel-waitlist-projects

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Laravel Waitlist: several projects

Use this skill when one application collects waitlists for more than one product, or when adding a second product to an existing taldres/laravel-waitlist setup. With one product, nothing here is needed: Waitlist::define() without a name describes the default project.

Primary Goal

  • every call, signup and mail acts for the right product, and nothing silently lands in the default project

Workflow

1. Define the projects

Each project has its own purposes, lists, fields and pages; nothing is inherited from the default project or another project. List names only need to be unique within a project.

// app/Providers/WaitlistServiceProvider.php, boot()
use Taldres\Waitlist\Definitions\ProjectDefinition;
use Taldres\Waitlist\Facades\Waitlist;

// The default project stays without lists, so a signup that forgets project()
// fails instead of landing on the wrong product. Its page catches unknown tokens.
Waitlist::define(fn (ProjectDefinition $project) => $project->urls(
    invalid: 'https://example.com/waitlist/oops',
));

Waitlist::define('rocket', function (ProjectDefinition $project): void {
    $project->purpose('waitlist', ['2026-10' => 'Email me when Rocket launches.']);
    $project->list('default', purpose: 'waitlist');
    $project->fields(fn () => ['source' => ['nullable', 'string', 'max:50']]);
    $project->urls(
        confirm: 'https://rocket.example/waitlist/confirm/{token}',
        unsubscribe: 'https://rocket.example/waitlist/leave/{token}',
        manage: 'https://rocket.example/waitlist/preferences/{token}',
    );
});

Waitlist::define('anvil', function (ProjectDefinition $project): void { /* same methods */ });
  • Keeping the main product in the default project and naming the others also works; then every call without project() acts on the main product.
  • Fields are per project and list: $project->fields() applies to every list, a list's own ->fields() comes on top. Fields of one project never reach another; a project without fields takes no metadata over HTTP.
  • Read the environment through config(), never env(). Defining a project again replaces it; the callback runs when the project is first needed.

2. Scope every call

Waitlist::project('rocket')->for('default')->add($email, $purposes);
Waitlist::project('rocket')->purposes('default', app()->getLocale());
Waitlist::project('rocket')->recipients('newsletter');
Waitlist::project('rocket')->report()->since(30)->totals();
  • Without project(), the facade acts on the default project, never on all.
  • Token methods (Waitlist::confirm(), unsubscribe(), withdrawConsent(), requestManageLink()) need no project: a token belongs to its entry.
  • An unknown project throws UnknownProjectException.

3. HTTP signups need a resolver

With WAITLIST_ROUTES_ENABLED=true, the signup, the wording and manage links requested by address act for the project a ProjectResolver returns. The default DefaultProjectResolver always returns default, so bind one:

namespace App\Waitlist;

use Illuminate\Http\Request;
use Taldres\Waitlist\Contracts\ProjectResolver;

class SiteResolver implements ProjectResolver
{
    public function resolve(Request $request): string
    {
        $host = parse_url((string) $request->headers->get('Origin'), PHP_URL_HOST) ?: $request->getHost();

        return match ($host) {
            'rocket.example' => 'rocket',
            'anvil.example' => 'anvil',
            default => abort(403),
        };
    }
}
// config/waitlist.php
'project_resolver' => App\Waitlist\SiteResolver::class,
  • A signup without list goes to waitlist.default_list (default) of the resolved project: give each project a list of that name, or always post list.
  • The resolver runs once per request, first, then the useWaitlist gate, both before the body is validated: the project decides which fields are accepted, and a refused request answers 401/403 unread.
  • An unknown project from the resolver: signup 422, wording 404.
  • Own controllers need no resolver; they call Waitlist::project(...) directly.
  • Never authenticate in waitlist.routes.middleware; it also guards token links.

3b. Who may call: the useWaitlist gate and servers with credentials

The package asks Laravel's gate useWaitlist before the signup, the wording and a manage link by address (never for token links), with (?Authenticatable $caller, string $project, WaitlistAction $action, ?string $list). The caller comes from the first guard in waitlist.authentication.guards that authenticates the request; Waitlist::caller($request) returns the same one. A guard config/auth.php does not define is refused, naming it; token links do not wait for it and are limited as a guest's request then.

  • Default: everyone may; a caller implementing HasWaitlistProject only for its own project (404 otherwise). With waitlist.authentication.required: guests 401, callers without a project 403.
  • Replace it with Gate::define('useWaitlist', ...) in any provider; it wins whichever provider boots first. Return a bool or a gate response; Response::denyWithStatus(401) and denyAsNotFound() reach the client.
  • Servers calling with tokens of their own: the token's model implements HasWaitlistProject, project_resolver is AuthenticatedProjectResolver (401 without a caller, 403 without a project or for an undefined one), authentication.guards names the token guard, authentication.required is on. Issuing and revoking the tokens stays with the app (Sanctum, Passport, a JWT guard); gate them too, e.g. Sanctum::authenticateAccessTokensUsing() with Waitlist::hasProject().
  • Sanctum abilities are read with tokenCan(), not can(), which asks gates and policies.
  • Such servers send for all their visitors from one address: the default limiters cap each server (rate_limits.caller_signup_per_minute, 120) and leave the limit per visitor to it, e.g. in a Next.js Server Action. Set authentication.client_ip_header (e.g. X-Waitlist-Client-Ip) to also limit per forwarded visitor; the header is never read from guests, and a value that is not an IP address is ignored.
  • $project->wordingFromCallers() lets them send the consent text with the version ({version, locale?, text}), so it lives only on the site: the first signup registers it with the server; a known version must read the same. The gate asks WaitlistAction::RegisterWording for such signups; by default only callers acting for the project get it.

4. Mail per project

Listeners read $event->entry->project and pick sender, mailer and template from it. Mail links already point at the project's own pages (urls()); a project without them uses the package routes, or null with routes off.

5. Requests about a person cover every project

Waitlist::allProjects()->personalData($email);
Waitlist::allProjects()->forget($email);
Waitlist::allProjects()->report();

waitlist:show, waitlist:forget, waitlist:prune and waitlist:privacy cover every project without --project, unless a --list narrows them to that list of the default project; waitlist:export, waitlist:wording and waitlist:forget --all act on the default project unless --project is given.

6. Projects in a database

For projects that come and go at runtime, or manage their own lists and wording, bind a ProjectCatalog (waitlist.catalog), usually extending StoredWordingCatalog, with policy(), fields() (field => rules; stored rules can only be strings), urlPattern(), projects() (must include default) and lists(), plus a resolver that maps a publishable key to a project. A catalog of your own replaces the definitions. Projects register wording with Waitlist::project($key)->registerWording().

Rules, References, and Templates

Examples

  • "Add a waitlist for our second product": keep the first product in the default project or name it too, add a Waitlist::define('<key>', ...) for the second, scope its calls with project(), and bind a resolver if signups come over HTTP.
  • "Delete everything about [email protected]": Waitlist::allProjects()->forget('[email protected]').

Anti-patterns

  • Enabling the package routes with several projects and no resolver: every HTTP signup goes to default.
  • Relying on the default gate to close an API: without waitlist.authentication.required or AuthenticatedProjectResolver, a caller that leaves out its token is a guest, and guests may.
  • Calling Waitlist::for($list) for a product that has a project of its own.
  • Expecting a project to inherit the purposes, lists, fields or pages of the default project.
  • Answering a person's erasure request with Waitlist::project($key)->forget() when they are on several products.

Individual skills in this repo

This repo contains 13 individual skills — each has its own dedicated page.

Taldres/laravel-waitlist

Use this skill when reviewing Laravel package compatibility across composer constraints, PHP versions, Laravel versions, Testbench versions, dependency stability lanes, Windows CI, or matrix-sensitive code and workflow changes.

Taldres/laravel-waitlist

Use this skill when creating or updating the bundled Laravel Boost skill under resources/boost/skills from the package implementation and package documentation. Trigger after public APIs, commands, config, routes, views, publish tags, README content, or examples change.

Taldres/laravel-waitlist

Use this skill when preparing Laravel package releases: CHANGELOG.md updates, generated release notes, GitHub release workflows, version checks, tags, release validation, or release automation changes. Never publish autonomously.

Taldres/laravel-waitlist

Use this skill when adding Laravel package capabilities or wiring them through the service provider: commands, migrations, routes, config merges, views, translations, assets, middleware, publish tags, workbench files, or console-only behavior.

Taldres/laravel-waitlist

Use this skill when writing, editing, fixing, or reviewing package tests with Pest 4/5 and Orchestra Testbench, including TDD, feature tests, unit tests, type coverage, arch tests, workbench behavior, commands, routes, config, migrations, and publishable resources.

Taldres/laravel-waitlist

Integrate taldres/laravel-waitlist in a Laravel application: define purposes, lists and fields, build the signup form, send the confirmation mail from events, add a preference page, and keep consent, retention and encryption intact.

Taldres/laravel-waitlist

Build the pages of a taldres/laravel-waitlist integration: the signup form with the registered wording, and the confirm, unsubscribe and preference pages, either in Laravel controllers (Blade, Livewire, Inertia) or in an SPA or static site over the package's JSON API, including CORS and bot protection.

Taldres/laravel-waitlist

Turn a taldres/laravel-waitlist waitlist into a launch: invite people in batches in signup order, let invited addresses register, announce the launch to everyone who agreed, and erase the list once its purpose is fulfilled.

Taldres/laravel-waitlist

Send the mails of a taldres/laravel-waitlist integration: the double opt-in confirmation, the preference page link, a welcome mail, launch mails and newsletters to the people who agreed, with the right unsubscribe links, one-click headers and a sender per project.

Taldres/laravel-waitlist

Operate a taldres/laravel-waitlist installation: answer access and erasure requests, withdraw a purpose on request, apply retention, erase a list once its purpose is fulfilled, rotate APP_KEY with waitlist:rekey, export a list, and check the setup before going live.

Taldres/laravel-waitlist

Keep a newsletter tool such as Brevo, Mailchimp or Mailcoach in step with a taldres/laravel-waitlist waitlist: add confirmed contacts per purpose, remove them on withdrawal and erasure, and carry unsubscribes made at the provider back into the waitlist via a webhook.

Taldres/laravel-waitlist

Build waitlist statistics with taldres/laravel-waitlist: signups and confirmations per day for charts, totals and confirmation rates for a period, the confirmed count on any past day, and how many people are on a list right now, for dashboards, admin pages and API endpoints.

Taldres/laravel-waitlist

Write feature tests for an application's taldres/laravel-waitlist integration: get the plain tokens from events, assert the confirmation and other mails, test the signup, confirm and unsubscribe flows in PHP and over the package's HTTP routes, and bypass rate limits and bot checks in tests.

Verwandte Skills