A modern self-invite gateway for Slack communities, built for Cloudflare Workers.
- Landing page with Slack member count and approximate online count
- Email-confirmed self-invite flow
- Permanent opt-out suppression link
- Code of Conduct acceptance gate before Slack invite link redirect
- Bot protection with Cloudflare Turnstile
- App-level rate limits and event audit logging (D1)
- Scheduled Slack stats refresh every 15 minutes
- Cloudflare Web Analytics beacon support
- Runtime: Cloudflare Workers + Hono
- Data: Cloudflare D1
- Email: Cloudflare
SendEmailbinding - Abuse control: Turnstile + app throttling (+ Cloudflare Rate Limiting rule)
- Stats source: Slack Web API
- Install dependencies:
npm install- Create D1 database:
npm run db:create-
Update
wrangler.jsoncwith yourdatabase_id. -
Apply migrations:
npm run db:migrate:local
npm run db:migrate:remote- Set required secrets:
npx wrangler secret put TURNSTILE_SECRET
npx wrangler secret put SLACK_BOT_TOKEN
npx wrangler secret put EMAIL_ENCRYPTION_KEY
npx wrangler secret put EMAIL_HASH_KEYFor local dev, copy .dev.vars.example to .dev.vars and fill values.
- Update
wrangler.jsoncvars:
APP_BASE_URLEMAIL_FROMTURNSTILE_SITE_KEYSLACK_SHARED_INVITE_URLCOC_VERSIONANALYTICS_TOKEN(optional)
- Start local dev:
npm run devCreate a Slack app/bot token with:
users:readusers:read.presence
- Verify sending domain and sender address.
- Configure DMARC/SPF/DKIM.
- Create a Turnstile widget.
- Use test keys for staging/local.
Create a dashboard rate-limit rule for:
- Path:
POST /api/invite-request - Limit:
20 requests / 10 minutes / IP
This Worker also enforces a local fallback IP/email limit in D1.
GET /landing pagePOST /api/invite-requestsubmit email + Turnstile tokenGET /confirm?token=...render explicit confirmation page (non-mutating)POST /confirmperform confirmation transition, then show CoCPOST /api/consentaccept CoC, redirect to Slack invite URLGET /optout?token=...render explicit opt-out confirmation page (non-mutating)POST /optoutsuppress future invitesGET /healthzhealth check
Defined in migrations/0001_initial.sql:
invite_requestsinvite_tokenssuppression_listworkspace_statsevent_log
npm testnpm run deployGenerate the changelog from Conventional Commit messages:
npm run changelogPreview without writing to CHANGELOG.md:
npm run changelog:previewThe generated output follows Keep a Changelog and Common Changelog so release notes stay:
- Consistent and easy to scan over time
- Focused on user-facing changes instead of internal noise
- Easy to consume in pull requests and release announcements
Changelog generation is based on Conventional Commits and provides:
Curated summary sections are produced for:
feat->Addedperf,refactor->Changedrevert->Removedfix->Fixeddocs->Documentationchore->Chores- Commits with security-related content in the body ->
Security
Each release also includes a ### Changelog section with the full commit list for the release range:
- Full-list entries use this format:
- [#123] <commit-subject> @<committer-id> [#123]appears only if a PR/issue reference exists in the commit message.<committer-id>is derived from GitHub noreply author email when available.- Types such as
test,ci,build,style, and other conventional types are included in the full list even when omitted from curated summary sections.
Breaking changes are preserved and marked as **BREAKING** in summary entries.
## [Unreleased] - YYYY-MM-DDis the current unreleased set of changes.- Section headers (
Added,Changed,Removed,Fixed,Documentation,Chores,Security) describe change categories. - Each bullet is one commit summary, optionally prefixed with scope (for example,
**app:**). ### Changelogincludes the compare link plus the full commit list for that release block.- Once release tags are added (for example,
v0.2.0), changelog sections naturally split by tag history andUnreleasedcontains only commits after the latest tag.
- This is intentionally single-workspace in v1.
- For non-enterprise Slack, final step is redirect to a managed shared invite URL.
- Opt-out suppression is indefinite until removed manually.
- Expired invite tokens are deleted after 30 days.
- Stale invite requests older than 90 days are deleted for statuses
PENDING_CONFIRM,CONFIRMED, andEXPIRED. - Event logs older than 180 days are deleted.
- Cleanup runs in every
scheduled()invocation.
- Slack API calls use retry with exponential backoff, jitter, and
Retry-Afterhandling for429/5xx. - Slack API calls use request timeouts to avoid long-hanging scheduled executions.
- Presence estimation tolerates partial failures and marks refresh stale when error rate is too high.
- Turnstile verification uses request timeout and fails closed on network/timeout errors.
- State-changing POST routes enforce same-origin
Origin/Refererchecks. - Invite request
email_hashandip_hashuse versioned HMAC hashing withEMAIL_HASH_KEY. - HTML responses use nonce-based CSP (no
unsafe-inline), with stricter defaults (default-src 'none',frame-ancestors 'none',object-src 'none'). - Security headers include HSTS, COOP/CORP, no-referrer policy, and no-store cache-control.
- Runtime config guard returns
503when required production env values are missing;/healthzreturns only coarseokstatus.