Expire something
Run a side effect (email, cleanup, notification) when an invitation, checkout hold, or magic link expires. Handler reads current state and skips if the user already acted.
Use this to
- Email a user that their Clerk invitation expired (Clerk has no webhook on time-based expiry)
- Release a held checkout cart 30 minutes after the customer started
- Invalidate a magic-link sign-in if the user doesn't click within 15 minutes
- Mark an upload as failed if processing hasn't completed within 10 minutes
Code
Clerk invitation: notify the user when it expires
// Clerk silently invalidates expired invitations but does not // fire a webhook on time-based expiry. DelayKit fills the gap: // schedule a job that runs the same moment Clerk stops accepting. const invitation = await clerkClient.invitations.createInvitation({ emailAddress: "user@example.com", expiresInDays: 7, }); await dk.schedule("expire-invitation", { key: invitation.id, delay: "7d", }); dk.handle("expire-invitation", async ({ key }) => { const invitation = await clerkClient.invitations.getInvitation(key); if (invitation.status !== "pending") return; // already accepted await sendEmail(invitation.emailAddress, "Your invitation has expired"); await db.pendingUsers.delete(invitation.id); });
Checkout hold: release stock if checkout stalls
// when the customer starts checkout, hold the inventory await dk.schedule("release-hold", { key: cart.id, delay: "30m", }); dk.handle("release-hold", async ({ key }) => { const reservation = await reservations.find(key); if (reservation.status !== "held") return; // already paid or cancelled await reservations.release(key); });
Cancel the expiry on early acceptance
// in your Clerk webhook or post-payment route await dk.unschedule("expire-invitation", invitation.id);
npm install delaykit
bun add delaykit