goldnead/statamic-lead-magnets
A Statamic addon for creating lead magnets—gated downloads, checklists, and resources that collect visitor details before delivery. Define magnets in the control panel, embed capture forms in your templates, and manage leads without leaving Statamic.
This is a lead-magnet / gated-delivery package: a visitor submits a form, a LeadMagnetResource decides how the thing is delivered (file, link, or — as of v3.12 — account access), and the package sends the confirmation and delivery mails from a template with placeholders.
Minimal path:
composer require the package, then publish config and migrations and run php artisan migrate. v3.12 adds one migration only — access_product, access_path, delivery_series on lead_magnet_resources, all nullable — so existing resources are untouched.notifications.webhook_url (new) and entitlements.access_source (new). Everything else is defaults that work.{{ access_url }} and whatever DeliveryVariables listeners add.Where to look when something is wrong: the delivery template first (90% of "the mail is wrong" is a template issue), then DeliveryService, then the resource row itself.
Treat the delivery method as a strategy, not a branch. File and link deliveries are self-contained; the new Access method inverts the model — instead of handing over a file, the reader is granted a product on an account. That means your host app owns identity. Bind Contracts\AccessSubjectResolver in a service provider:
$this->app->bind(AccessSubjectResolver::class, function () {
return new class implements AccessSubjectResolver {
public function resolve(LeadMagnetResource $resource, array $data): AccessSubject
{
$user = User::firstOrCreate(['email' => $data['email']]);
return new AccessSubject($user, URL::signedRoute('login', ['user' => $user->id]));
}
};
}
The resolver must find or create the account and return a sign-in link — the addon creates no users. Then set product slug + landing path on the resource and put {{ access_url }} in the delivery template.
Use DeliveryVariables instead of subclassing DeliveryService. This is the cleanest extension point in the release: register a listener that appends placeholders to both confirmation and delivery mails. Keep listeners pure — return variables, don't send mail, don't mutate the resource.
Wire the mail series when you have the automations addon. delivery_series holds an automation handle whose first mail replaces the delivery mail for a first-time reader. Let the series trigger from entitlements.granted on the product, so access grant and mail sequence are driven by one event rather than two code paths. Design for the fallback: without the addon, with the automation toggled off, or for a returning reader, the normal delivery mail must still be correct on its own. Don't put series-only content in the delivery template.
Route operational visibility through the webhook. notifications.webhook_url posts every request to a Discord-compatible endpoint. Point it at a per-environment channel — a #leads-staging and a #leads-prod — and use it as your smoke test: if the webhook fires, the funnel works end to end.
firstOrCreate on email, and make the sign-in link short-lived and route-signed. If you throw from the resolver, the reader gets nothing and the failure surfaces only in logs — wrap in try/catch and fall back to a support message.DeliveryVariables fires for both mails. Distinguish confirmation vs delivery in the payload; returning a heavy or slow value (a DB query per placeholder) runs on both. Cache within the request.delivery_series and access_* are empty, so nothing infers them. You must opt in per resource.config:clear after adding notifications.* / entitlements.access_source. Cached config is the usual reason a new key "does nothing".entitlements.access_source before assuming access grants are attributed correctly in reporting.How can I help you explore Laravel packages today?