Executives join a dozen Slack channels and read none of them. FocusGate reads the last 24 hours of every channel a person can see and emails them one short brief at the hour they pick: what happened, what is blocked, what is due and who owns it.
The real web app, recorded headless on 127.0.0.1 with a local Postgres. Clerk, Slack and Gemini are stubbed. Every person, company and address is invented (
.test and .example domains).
The core service pulls every message from the last 24 hours, swaps Slack user IDs for real names and sends the chat log to Gemini with one structured prompt. The answer becomes an HTML email with fixed sections: executive summary, key achievements, blockers, incidents, upcoming deadlines, team updates, recommendations and action items.
Rendered by the real pipeline (generate_newsletter_content and core/templates/newsletter.html) from five invented Slack channels. Only Slack and the model reply were stubbed.
Each person chooses when the brief arrives. The browser's time zone is saved with the choice, and a Postgres function (core/sql/get_scheduled_users.sql) works out who is due in the next window.
New users do not have to wait until tomorrow. The dashboard button asks the core API to build and send a brief straight away.
- Add to Slack OAuth with read-only history scopes. The user token is saved per user in Postgres, so FocusGate only reads what that person can already read.
- A team brief: set
TEAM_SLACK_CHANNEL_IDand a Celery job posts a brief for the whole team to that channel every morning at 05:00 UTC. - A
/focusgateslash command that posts a brief into the current Slack channel on demand. - Stripe billing (optional): pricing page, checkout, customer portal and a webhook that syncs subscriptions into Postgres.
- Clerk sign-in with a webhook that creates the user row in Postgres.
web/ Next.js 14 (App Router) + Clerk + Drizzle + Stripe sign-in, Add to Slack, delivery time, billing
core/ FastAPI + Celery + Redis Slack fetch, Gemini summary, email send
├─ app/integrations/slack read 24 h of history, resolve names, build the brief
├─ app/core/ai the morning brief prompt
├─ app/core/scheduler Celery beat: produce every hour, consume every minute
├─ app/core/emailing Mailtrap sending API
└─ sql/ get_scheduled_users() for the shared Postgres
Both apps share one Postgres database. The web app owns the schema (Drizzle migrations in web/src/db/migrations/). Every hour, Celery beat calls get_scheduled_users() and puts the users who are due in a Redis queue. A second job runs every minute and sends whatever is ready.
You need Node 18+, Python 3.12 (the 2025 lockfile does not install on 3.14), Poetry, Docker (for Postgres and Redis), and accounts for Clerk, a Slack app, Google AI Studio (Gemini) and Mailtrap. Stripe is optional.
git clone https://github.com/enzihub/focusgate && cd focusgate1. Database. Start Postgres and the local Neon HTTP proxy that the web app's driver talks to, then run the migrations and the scheduler function:
cd web
export POSTGRES_PASSWORD=choose-a-local-password
export PG_CONNECTION_STRING="postgres://postgres:$POSTGRES_PASSWORD@postgres:5432/main"
docker compose -f src/db/docker-compose.yml up -d
cp .env.example .env.local # fill it in (see Configuration)
npm ci
npm run db:migrate
psql "postgres://postgres:$POSTGRES_PASSWORD@localhost:5432/main" -f ../core/sql/get_scheduled_users.sql2. Web app on http://localhost:3000:
npm run dev3. Core service. Copy core/.env.example to core/.env.development and fill it in, then run each of these in its own terminal:
cd ../core
poetry install --no-root
poetry run uvicorn main:app --port 8000
poetry run celery -A app.core.scheduler.job_tasks worker --loglevel=info
poetry run celery -A app.core.scheduler.job_tasks beat --loglevel=infoOr run the API, worker, beat and Redis together with docker compose up in core/.
The email template test needs no services: cd core && poetry run pytest tests/test_newsletter_template.py.
Note
In your Slack app, set the OAuth redirect URL to <SITE_URL>/auth/slack/callback and add the user scopes channels:history, groups:history, mpim:history, im:history, channels:read, groups:read, mpim:read, im:read and users:read.
Every value in the example files is empty. Nothing personal ships with the repo.
| Variable | Where | What it is |
|---|---|---|
SITE_URL, NEXT_PUBLIC_SITE_URL |
web | Public URL of the web app (Slack OAuth redirect) |
DATABASE_URL |
web, core | The shared Postgres database |
FOCUSGATE_CORE_API_URL, FOCUSGATE_CORE_API_KEY |
web | Where the core runs, and the key it expects in X-API-Key |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, SIGNING_SECRET |
web | Clerk sign-in and its user webhook |
NEXT_PUBLIC_SLACK_CLIENT_ID, SLACK_CLIENT_SECRET |
web | The Slack app used for "Add to Slack" |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PAYMENT_LINK1 |
web | Optional billing |
NEXT_PUBLIC_SUPPORT_EMAIL, NEXT_PUBLIC_DEMO_BOOKING_URL |
web | Optional footer contact and booking link |
NEXT_PUBLIC_AUTHORIZED_EMAILS |
web | Optional allow-list for internal tools |
NEXT_PUBLIC_GA_ID, NEXT_PUBLIC_GTM_ID |
web | Optional analytics. Off when empty |
API_KEY |
core | Shared secret the web app sends in X-API-Key |
REDIS_URL, CELERY_BROKER_URL, CELERY_RESULT_BACKEND |
core | Redis for Celery and the send queue |
PRODUCE_INTERVAL_SECONDS, CONSUME_INTERVAL_SECONDS |
core | Scheduler timing (defaults 3600 and 60) |
GEMINI_API_KEY |
core | Writes the brief |
OPENAI_API_KEY |
core | Optional, not used by the default pipeline |
CLERK_SECRET_KEY |
core | Optional Clerk lookups |
SLACK_SIGNING_SECRET, SLACK_BOT_TOKEN, TEAM_SLACK_CHANNEL_ID |
core | Slash commands and the optional team brief |
MAILTRAP_API_TOKEN, FROM_EMAIL, FROM_NAME, EMAIL_SUBJECT |
core | Email sending |
LOGO_URL, APP_URL |
core | Logo and links inside the email |
SENTRY_DSN |
core | Optional error tracking |
FocusGate was built by Enzi Studio in 2025 and is shared as-is. It is not maintained and it is not a hosted service, so expect to update dependencies before you deploy it. The google-generativeai package it uses has since been deprecated in favour of google-genai. The old marketing site was a Framer export full of tracking scripts, so it is not included. docs/ is a new page made for this release.
Built by Enzi Studio. Contributors to the original repositories: @RukshanJS · @sun2ii · @bb-xops · @harrythentrepreneur · @kavishkanimsara · @ZainAli104
The web app, website and images use the Geist typeface (SIL Open Font License 1.1).
MIT © 2025-2026 Enzi Studio (Harry Edwards)





