Skip to content

hasanhatemdev/gateqr

Repository files navigation

GateQR

Self-hosted WhatsApp + email event invitations with RSVP and QR check-in.
Send invitations over WhatsApp (and email), collect Accept/Decline replies, auto-issue a unique QR code to everyone who accepts, and scan those QRs at the door to mark attendance.

License: MIT Node >= 20">https://img.shields.io/badge/node-%E2%89%A520-brightgreen"/> Docker Compose PRs welcome

Sample WhatsApp invitation Unique QR code composited onto your design Door scanner marks the guest Registered

Bundled sample artwork β€” swap in your own. No design skills required.


Features

  • πŸ“¨ WhatsApp invitations via the official Cloud API (Graph) β€” called directly with fetch, no SDK.
  • βœ… Accept / Decline RSVP β€” guests reply with a tap; the webhook records it.
  • 🎫 Automatic QR issuance β€” everyone who accepts gets a unique QR, composited onto your own artwork (auto box-detection finds where the QR goes).
  • πŸ“· Door scanner PWA β€” open /scan on any phone; first scan β†’ green Registered, repeat β†’ Already Attended.
  • πŸ—‚οΈ Any number of categories (general / partner / VIP / private / …) β€” each with its own template, artwork, and optional venue, all defined in one place.
  • πŸ”Ž Live admin + searchable guest list β€” counts, CSV export, search by name/email/phone, protected by a token.
  • βœ‰οΈ Optional email channel (Resend) β€” run WhatsApp-only, email-only, or both.
  • ⏱️ Throttled bulk sending, delivery/read tracking, and a check-in gate (attendance only counts after the event opens).
  • 🌍 Any language + RTL β€” all copy lives in config; ships English, flip UI_DIR=rtl for Arabic/Hebrew/…
  • 🐳 Self-hosted, one command β€” docker compose up brings up Postgres, Redis, and the app.

How it works

import list ─► guests (Postgres, delivery='queued')
                    β”‚  enqueue β†’ [invites queue]
                    β–Ό
   send worker ─► WhatsApp template (your artwork + Accept/Decline buttons)
                    β”‚  webhook: button reply
       accept ──────┴─► qr worker ─► QR composited on your design ─► R2 ─► sent to guest
       decline ────────► polite apology message (no location)
                    β–Ό
   guest opens QR link  <BASE_URL>/c/<token>  ─► door scanner marks attendance
                                                 (gated by CHECKIN_OPENS_AT)

Screenshots

Guest list & campaign search Check-in (before the event)
Admin guest list with live status Invitation confirmed screen

Quick start

Requirements: Docker (for Postgres + Redis). Node β‰₯ 20 if you want to run scripts outside Docker.

git clone <your-repo-url> gateqr && cd gateqr
cp .env.example .env          # works as-is for local demo β€” no credentials needed
docker compose up             # Postgres + Redis + app (migrates on start)

Open http://localhost:3000 β€” you'll get the admin dashboard, /scan (the door scanner), and /list (searchable guest list). The app boots even with no WhatsApp/email/R2 keys; those channels simply stay off until you add credentials.

To try the QR pipeline with the bundled sample artwork:

npm install
npm run make-placeholder-designs   # sample invitation + QR design in public/images/
npm run test-qr                    # composites a QR onto the sample β†’ tmp/qr-*.png

Configuration

Everything event-specific lives in lib/config.js and .env β€” nothing is hardcoded.

Categories

Define any number of categories in one place (CATEGORY_DEFS in lib/config.js). Each has a key, a label, optional free-text aliases (for importing spreadsheets in any language), and β€” via env β€” a template name, invitation image, QR design, and optional per-category location:

export const CATEGORY_DEFS = [
  { key: 'general', label: 'General' },
  { key: 'vip',     label: 'VIP', aliases: ['premium', 'gold'] },
  // add as many as you like…
];

Per-category overrides use the key: TEMPLATE_VIP, INVITE_VIP, QR_DESIGN_VIP, LOCATION_URL_VIP, etc.

Artwork

Drop your own images in public/images/ (or point INVITE_<CAT> / QR_DESIGN_<CAT> at them). The QR design just needs a white square where the code goes β€” then let the detector find it:

node scripts/detect-qr-box.js public/images/your-qr-design.png
# β†’ prints x / y / size; paste into QR_LAYOUT (or the category's qrBox) in lib/config.js

Message copy & language

Invitation body, button labels, QR caption, decline message, and all page/scanner text are in config (English defaults). Override any via env (TEMPLATE_BODY, BTN_ACCEPT, QR_CAPTION_BODY, …). For a non-English UI set UI_LANG and UI_DIR (e.g. UI_DIR=rtl), and TEMPLATE_LANG to match your approved WhatsApp template language.

Location

The venue/map link is sent only in the QR message after a guest accepts β€” never to decliners. Set a default LOCATION_URL, per-category LOCATION_URL_<CAT>, or per-list overrides (LOCATION_BY_TAG).

Admin access

/admin and /list are protected by ADMIN_TOKEN (HTTP Basic β€” any username + the token, or Authorization: Bearer <token>). If it's unset the pages are open (dev only) and the server warns at boot. Set it in production.

Channels are optional

WhatsApp, email, and R2 each activate only when their keys are present. Missing keys disable that channel with a clear log line instead of crashing.

WhatsApp setup

The one part that needs a bit of patience is Meta's template approval. Full walkthrough in docs/templates.md:

  1. Create a Meta app + a WhatsApp Business number (Cloud API), and copy the IDs/tokens into .env.
  2. Create one marketing template per category with an image header + two quick-reply buttons: npm run create-templates.
  3. Once approved, put the exact template names in .env (TEMPLATE_<CAT>), point your webhook at https://<public-host>/webhook, and subscribe to messages.

Header images are supplied at send time (media id), so you can change artwork without re-approval. New template text/buttons need a new approved template. Marketing templates hit a per-recipient frequency cap (#131049, temporary β€” retry later); #131026 = number not on WhatsApp.

Commands

Command What
docker compose up Postgres + Redis + app (migrates, serves)
docker compose --profile workers up -d worker start the send/QR workers (needs credentials)
npm run migrate apply db/schema.sql
npm run import -- <file.xlsx> [--sheet NAME] [--dry] import a guest list
npm run enqueue -- [--limit N] [--category vip] [--all] queue invitations
npm start / npm run workers run the server / both workers locally
npm run make-placeholder-designs / npm run test-qr generate sample art / test the QR pipeline

Design notes

  • Tokens: random 24-byte base64url, unique per guest, the only thing in the QR. Valid iff present in guests β€” no signature math, not enumerable. Only staff resolve tokens, so guests can't self-check-in.
  • Idempotency: accept transitions pending β†’ accepted exactly once (a second tap won't re-issue a QR); check-in is an atomic UPDATE … WHERE checked_in_at IS NULL.
  • Webhook security: POST /webhook verifies X-Hub-Signature-256 against APP_SECRET.
  • Ports: local Postgres is on host :5433, Redis on :6380 to avoid clashing with native installs.

Responsible use

GateQR is for legitimate, opt-in event invitations only β€” inviting people who expect to hear from you about an event. It is not for unsolicited messaging or spam. You are responsible for complying with WhatsApp/Meta's Business and messaging policies and with the privacy and anti-spam laws that apply to you and your guests. Be a good host, not a spammer.

Tech stack

Node.js (ES modules) Β· Fastify Β· BullMQ + Redis Β· PostgreSQL Β· Cloudflare R2 (S3-compatible) Β· qrcode + sharp Β· libphonenumber-js Β· xlsx Β· Resend (email). The WhatsApp Cloud API and Resend are called directly with fetch β€” no SDK wrappers.

Roadmap

  • Optional public RSVP web page (in addition to WhatsApp buttons)
  • Multi-event support in a single instance
  • Pluggable storage (local disk / S3 / R2) for QR images
  • Import from Google Sheets / CSV in the UI

Ideas and PRs welcome.

Contributing

See CONTRIBUTING.md. In short: ES modules only, no SDK wrappers, config-driven, and never commit real data or secrets.

License

MIT Β© Hasan Hatem


Topics: whatsapp Β· whatsapp-cloud-api Β· rsvp Β· qr-code Β· event-management Β· check-in Β· invitations Β· self-hosted Β· nodejs Β· fastify

About

Self-hosted WhatsApp + email event invitations with RSVP and QR check-in.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages