Live app: https://trydevin.ai
Dispense credit codes to event participants (hackathons, conferences, meetups). Admins create events with a list of eligible emails and a pool of unique codes; attendees visit the event's URL (e.g. /hackathon-1), enter their email, and receive a code if their email is on the list.
- Next.js (App Router) — deployed on Vercel
- Convex — database & realtime backend
- Clerk — admin authentication
- Tailwind CSS — monochromatic dark theme
- Admin creates an event (slug auto-generated from the name, or set/edited manually).
- Admin pastes in the list of eligible emails and the pool of unique codes.
- Attendees visit
/<slug>and sign in with Clerk (Google or email code) to prove they own their email address. - If the verified sign-in email is on the list, they're assigned an unclaimed code (idempotent — the same email always gets the same code back).
- If not, they can't claim.
- Cross-event duplicate flagging: when emails are uploaded (CSV/XLSX or pasted), any address that already appears in another event's eligible list is not added directly. It goes to a "Flagged for review" section on the manage-event page, where the organizer must approve or reject each one individually. Both decisions are recorded in the audit log. Upload results report the number of flagged addresses.
- App-wide email blacklist: global admins manage a blacklist at
/admin/blacklist. A blacklisted address is rejected on every path that would add it to an event's eligible list after being blacklisted — uploads skip it (reported asN rejected (blacklisted)in the upload toast), and approving a flagged email or waitlist request for it fails with an error. Each rejected upload attempt is recorded and shown to event admins in a read-only "Blacklisted" card on the manage-event page. Addresses already on an event's list before being blacklisted are untouched; un-blacklisting an address clears its recorded hits.
/— public list of events (newest first)/<slug>— claim page for an event (requires signing in to verify email ownership)/admin— admin dashboard (Clerk-protected): create events/admin/events/new— create an event with new Stripe codes, a saved batch, or an empty code pool/admin/codes— browse, filter, view, export, and assign saved Stripe code blocks (system admins only)/admin/codes/new— generate and save a code block without creating an event (system admins only)/admin/events/<id>— manage an event: edit name/slug/description, emails, codes, see claim stats, review flagged emails/admin/blacklist— app-wide email blacklist (global admins only)/sign-in— Clerk sign-in page (kept same-origin so protected-route redirects don't break client navigations)
npm install
cp .env.example .env.local # fill in Convex + Clerk keys
npx convex dev # in one terminal
npm run dev # in another- In Clerk, create a JWT template named
convex(see Convex Clerk docs). Include theemailandemail_verifiedclaims. - Set the issuer domain on your Convex deployment:
npx convex env set CLERK_JWT_ISSUER_DOMAIN https://<your-app>.clerk.accounts.dev
Admin access is controlled by an email allowlist stored in Convex (admins table), managed at /admin/admins:
- System admins require a verified Clerk email on the allowlist. An empty allowlist grants no system-admin access.
- Before the first sign-in, add your normalized, lowercase email to the
adminstable through the trusted Convex dashboard. - Existing system admins manage the allowlist at
/admin/admins. The app prevents removal of the last system admin. - Event admins can manage their assigned events. They cannot create events, generate Stripe codes, or access Stripe batch history.
- If you lose access, restore an admin entry through the trusted Convex dashboard. Do not delete the allowlist.
The allowlist is per Convex deployment (dev and prod each have their own admins table).
- Set
STRIPE_API_KEYin the Convex deployment environment through the Convex dashboard. Do not put the key in aNEXT_PUBLIC_variable. - Use a restricted Stripe key with write access to Coupons and Promotion Codes.
- Deploy the Convex schema and functions before using the code library. For development, run
npx convex dev --onceagainst the intended deployment. - Start with a Stripe test key. The creation form shows the mode and requires explicit confirmation for live generation.
Each batch contains 1–500 single-use USD promotion codes. Prefixes and expiration dates are optional. The backend saves progress and retains batch history.
To save codes without an event, open Codes → New code block. Review the details and confirm generation. The block appears in the library automatically, with its progress and saved codes. Event assignment is optional. The Use saved field links to creation in a new tab, so the event draft stays open.
If generation fails, resume the same batch within 23 hours of its first attempt. After that window, reconcile the batch in Stripe before creating replacements. Deleting an event or removing its codes does not revoke the Stripe promotion codes. The app does not synchronize redemption or revocation changes from Stripe.
Use npx convex deploy --cmd 'npm run build' as the build command (with CONVEX_DEPLOY_KEY set to a production deploy key) per the Convex Vercel guide — it pushes functions to the prod Convex deployment and injects NEXT_PUBLIC_CONVEX_URL at build time.
Clerk production instances require a domain you own, but the development instance works from any origin — so a vercel.app deploy runs fine on dev keys (with the "Development mode" badge and dev usage limits):
- In Vercel, set the dev keys (
pk_test_...,sk_test_...),NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in, andCONVEX_DEPLOY_KEY(production deploy key), withnpx convex deploy --cmd 'npm run build'as the build command. - Point the prod Convex deployment at the dev Clerk issuer:
npx convex env set CLERK_JWT_ISSUER_DOMAIN https://<your-app>.clerk.accounts.dev --prod
- Before the first sign-in, add your verified email to the production
adminstable through the trusted Convex dashboard.
Dev and prod are fully separate instances in both Clerk and Convex — none of the dev config carries over. The "Development mode" badge in the Clerk UI goes away once the app runs on production (pk_live_) keys.
Clerk (dashboard → create Production instance):
- Set your production domain; add the DNS records Clerk asks for (
clerk.<domain>Frontend API CNAME, plus the email DNS records if you use email-code sign-in). This must be a domain you own —*.vercel.appdomains can't be used because you can't create DNS records under them. A subdomain you control (e.g.credits.yourdomain.com) works. Enter it bare, withouthttps://. - Google OAuth in production requires your own credentials: create a Google OAuth client and paste its ID/secret into Clerk's Google connection (dev instances use Clerk's shared ones).
- Recreate the
convexJWT template on the production instance (same claims as dev — see Clerk + Convex auth above). - In Vercel, set
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY(pk_live_...),CLERK_SECRET_KEY(sk_live_...), andNEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in.
Convex (prod deployment):
- Set the issuer domain on the prod deployment — for a Clerk production instance this is your custom domain, not
*.clerk.accounts.dev:npx convex env set CLERK_JWT_ISSUER_DOMAIN https://clerk.<your-domain> --prod
- Before the first sign-in, add your verified email to the production
adminstable through the trusted Convex dashboard. - Events, emails, and codes live per deployment; re-create/upload them in prod.