Skip to content

Repository files navigation

MeetLog

AI Meeting Intelligence — upload a recording or paste a transcript, get a structured summary with decisions, action items, open questions, and blockers, then push the result to Notion and Slack.

Features · Tech Stack · Quick Start · Environment · Integrations · Deployment


Table of Contents

  1. About
  2. Features
  3. Tech Stack
  4. Monorepo Layout
  5. Quick Start
  6. Environment Variables
  7. Integrations
  8. Database & Migrations
  9. Available Scripts
  10. Project Architecture
  11. Production Build & Deployment
  12. Troubleshooting
  13. Security Notes
  14. Roadmap

About

MeetLog turns long, messy meetings into a structured artefact you can actually act on. Upload an audio file (or paste a raw transcript) and the system will:

  1. Transcribe the audio with AssemblyAI.
  2. Extract decisions, action items, open questions, blockers, and a summary using Google Gemini.
  3. Store everything in Postgres and let you browse, search, and edit from a Next.js dashboard.
  4. Push the result to Notion (as a database page) or Slack (as a formatted message).

The codebase is a TypeScript monorepo with two apps — web (Next.js 16 App Router) and api (Express + Drizzle ORM) — plus one shared types package.

Features

  • Two ingest paths: upload an audio file, or paste a raw transcript.
  • Auto-generated meeting view: summary, decisions, action items with owner & deadline, open questions, blockers, speakers with turn counts.
  • Action item tracking: checkbox to mark complete; overdue owners/deadlines surface in red.
  • Full transcript search: in-page search with prev/next match navigation.
  • PDF export and opt-in share links for any meeting.
  • Notion integration: push a completed meeting to a Notion database with one click; built-in connection test.
  • Slack integration: incoming-webhook based, no OAuth app needed; built-in test.
  • Dashboard, Action Items, and Analytics pages with TanStack-Query caching.
  • Monthly transcription quota: 2 free hours per user, enforced server-side, visible in the sidebar.
  • Polished UI: dark mode, responsive layout, micro-animations, in-product help guides.

Tech Stack

Frontend (apps/web)

Next.js 16 · React 19 · TypeScript · Tailwind CSS v4 · TanStack Query v5 · Zustand · React Hook Form + Zod · React Dropzone · Recharts · Lucide React · date-fns · sonner · framer-motion

Backend (apps/api)

Express 4 · TypeScript · Drizzle ORM + drizzle-kit · PostgreSQL · Better Auth · BullMQ (or Postgres queue) · AssemblyAI · @google/genai · Cloudinary · @notionhq/client · pdfkit · Multer · Zod · express-rate-limit

Shared

@meetlog/shared (packages/shared) — TypeScript types shared between web and api.

Monorepo Layout

meetlog/
├── apps/
│   ├── web/         # Next.js 16 frontend
│   └── api/         # Express backend (Drizzle, queue worker)
├── packages/
│   └── shared/      # TypeScript types shared by both apps
├── package.json     # Root workspaces + cross-app scripts
└── README.md

In production, a single Node process serves the API and the built Next.js standalone output on one port.

Quick Start

Prerequisites

  • Node.js 20+ (Next.js 16 requires it) and npm 10+.
  • PostgreSQL 14+ running locally, or a hosted Postgres URL.
  • A free Gemini API key from aistudio.google.com/apikey.
  • An AssemblyAI key (optional; only needed for audio uploads).
  • A Cloudinary account (required for audio uploads).

Setup

git clone <your-fork-url> meetlog
cd meetlog
npm install

createdb meetlog                       # or: psql -U postgres -c "CREATE DATABASE meetlog;"
npm run db:migrate

cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env.local
# Set DATABASE_URL and BETTER_AUTH_SECRET in apps/api/.env

npm run dev

Open http://localhost:3000 for the web app; the api runs at http://localhost:3001. In dev, the web app proxies /api/* to the api via a Next.js rewrite.

Environment Variables

Two env files — one per app. NEXT_PUBLIC_* vars are inlined at build time and require a rebuild to change.

apps/api/.env — backend

Variable Required? Default Purpose
DATABASE_URL yes postgresql://localhost:5432/meetlog Postgres connection string.
BETTER_AUTH_SECRET yes dev-only fallback JWT signing secret. Generate with openssl rand -base64 32.
PORT no 3001 HTTP port the api binds to.
NODE_ENV no development development, production, or test.
APP_URL no http://localhost:3000 Public URL of the web app (CORS, OAuth redirects, share links).
BETTER_AUTH_URL no http://localhost:3001 Public URL of the api (Better Auth needs its own origin).
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET no "" Google OAuth credentials (enable "Sign in with Google").
CLOUDINARY_CLOUD_NAME / CLOUDINARY_API_KEY / CLOUDINARY_API_SECRET no "" Cloudinary credentials. If any is missing, audio uploads fall back to the api server's local disk.
ASSEMBLYAI_API_KEY no "" Audio transcription. Without it, only the "paste transcript" path works.
GEMINI_API_KEY no "" Google Gemini API key. Get one at aistudio.google.com/apikey.
GEMINI_MODEL no gemini-2.5-flash-lite Gemini model name (free tier).
QUEUE_BACKEND no auto auto, postgres, bullmq, or memory. auto uses BullMQ for safe Redis URLs and the durable Postgres queue otherwise.
REDIS_URL no "" Redis URL for BullMQ. Docker Compose sets this to redis://redis:6379.
ALLOW_UPSTASH_REDIS no false Required opt-in before BullMQ uses an Upstash URL (avoids Upstash command-volume costs).
NOTION_TOKEN_ENCRYPTION_KEY no "" 32-byte hex string for AES-256 encryption of stored Notion tokens. Generate with openssl rand -hex 32.
MAX_UPLOAD_MB no 500 Max audio upload size in MB. Enforced server-side and echoed to the browser.

See apps/api/.env.example for the full list, including queue tuning and email (Resend) vars.

Resolution order for the AI service (highest priority first): Gemini API (if GEMINI_API_KEY is set) → non-AI keyword fallback (always available, lower quality but useful for development).

apps/web/.env.local — frontend

Variable Required? Default Purpose
NEXT_PUBLIC_API_URL no http://localhost:3001 Base URL the browser uses for /api/* requests. In production where the api serves the web bundle, set this to a relative path or the deployed origin.
NEXT_PUBLIC_USE_MOCK no false Set to true to bypass the api and use in-browser mock fixtures (apps/web/src/lib/mockApi.ts).
NEXT_PUBLIC_MAX_UPLOAD_MB no 500 Client-side upload cap. Must agree with the api's MAX_UPLOAD_MB.

Integrations

Notion

Each user connects their own Notion account from Settings → Integrations → Notion: create an internal integration at notion.com/developers/connections, copy the access token and database id, paste both into Settings, and click Connect. Push to Notion creates a new page in the database; a Test button creates and archives a sample page to confirm the connection. The token is encrypted at rest with NOTION_TOKEN_ENCRYPTION_KEY (AES-256); rotating the key invalidates all stored tokens.

Slack

Slack uses incoming webhooks: create a webhook in your Slack workspace, paste the URL into Settings, and MeetLog posts a Block Kit message to that channel when you click Send to Slack on a meeting. A Test button posts a "✅ MeetLog test message" to confirm the webhook is live.

Email (planned)

Resend is wired into the env schema but not yet used. When email reminders ship, RESEND_API_KEY and RESEND_FROM_EMAIL will be the only required vars.

Database & Migrations

The schema lives in apps/api/src/db/schema.ts; SQL migrations are in apps/api/drizzle/. Apply them with:

npm run db:migrate

To generate a new migration after editing the schema:

cd apps/api
npm run db:generate     # writes a new .sql file to drizzle/
npm run db:push         # apply directly to the dev database

Available Scripts

Command What it does
npm run dev Start web + api concurrently with live reload. Web at :3000, api at :3001.
npm run build Build both apps for production.
npm run start Start the production api. In production it also serves the built web bundle.
npm run typecheck Typecheck the shared types and both apps.
npm run db:migrate Apply all SQL migrations to the database.

Per-app scripts (e.g. npm run dev:web, npm run lint, npm run worker) live in the relevant apps/*/package.json.

Project Architecture

  • Server state (meetings, action items, settings, analytics) lives in TanStack Query, keyed in apps/web/src/lib/hooks.ts so mutations can invalidate exactly the right queries.
  • Client state (sidebar, modals, auth) lives in Zustand stores in apps/web/src/store/.
  • Form state is local React state, mirrored to the server only on submit.
  • Data model: User / Session / Account (Better Auth) · Meeting (with status) · MeetingAnalysis (Gemini output) · ActionItem (owner, deadline, completed) · TranscriptUtterance (speaker-tagged segments) · UserSettings (template, Slack webhook, encrypted Notion token + database id).

Production Build & Deployment

npm install
npm run build
npm run start

This builds the Next.js app with output: 'standalone' into apps/web/.next/standalone/, transpiles the api with tsc into apps/api/dist/, and starts a single Node process that serves the API, static assets, uploaded files, and the Next.js HTML. A health check is available at GET /api/health.

The default deployment is single-server (one Node process, one port). To split:

  • Web → deploy apps/web/.next/standalone/ to Vercel, Cloudflare Pages, or a Node host.
  • Api → deploy apps/api/dist/ to a Node host.
  • Set NEXT_PUBLIC_API_URL in the web env to the api's public origin.

Queue backends: bullmq (Redis, with separate IORedis connections for Queue vs Worker) is used when REDIS_URL is set; otherwise a durable Postgres queue (processing_jobs table, FOR UPDATE SKIP LOCKED) is used. The memory backend is in-process only. Avoid Upstash for BullMQ unless you accept its command-volume cost — the Postgres queue is the recommended fallback for Upstash deployments.

Troubleshooting

  • psql: command not found — Install the Postgres client (macOS: brew install libpq; Windows: use the Stack Builder from the Postgres installer).
  • Error: P1001: Can't reach database serverDATABASE_URL is wrong, the database isn't running, or the connection is blocked. Test with psql $DATABASE_URL -c "select 1".
  • BetterAuthError: Invalid originBETTER_AUTH_URL and APP_URL need to be the public origin the browser sees (in dev: http://localhost:3001 and http://localhost:3000).
  • Notion push failed: Could not find database — Database id is wrong, or the integration wasn't invited. In Notion, open the database → •••Connections → invite MeetLog.
  • Slack responded with 404 — Webhook was deleted or revoked. Re-create it in Slack and paste the new URL in Settings.
  • MAX_UPLOAD_MB not enforcedMAX_UPLOAD_MB (api) and NEXT_PUBLIC_MAX_UPLOAD_MB (web) must agree. Set both to the same value and restart.
  • TypeScript errors after npm install — Run npm run typecheck; often a rm -rf node apps/*/node_modules packages/*/node_modules && npm install resolves peer-dep drift.
  • Dashboard redirect loop to /auth/signin — Usually a route-ordering bug. Check the api log for a Postgres 22P02 error followed by ECONNREFUSED in the web log; the named route (e.g. /api/meetings/usage) must be registered before any catch-all /:id handler.

Security Notes

  • Never commit .env files — they are git-ignored by default.
  • Rotate secrets in productionBETTER_AUTH_SECRET, NOTION_TOKEN_ENCRYPTION_KEY, and any third-party API key. BETTER_AUTH_SECRET rotation invalidates all existing sessions.
  • Notion access tokens are encrypted at rest (AES-256). Back up NOTION_TOKEN_ENCRYPTION_KEY — losing it permanently locks all stored tokens.
  • Slack webhook URLs are stored in plaintext (Slack expects this). If leaked, revoke and create a new one.
  • Rate limiting is enabled on the api via express-rate-limit.
  • CORS is configured to allow only APP_URL. Don't widen to * in production.

Roadmap

  • Email reminders for upcoming action item deadlines (Resend is wired up).
  • Calendar integration to auto-detect upcoming meetings.
  • Two-way Slack sync to mark action items done from a message reaction.
  • Speaker diarization improvements and editable speaker labels.
  • Multi-user workspaces with shared meeting libraries.
  • Mobile app (React Native, reusing the same API).

About

MeetLog is an AI meeting intelligence platform that turns raw audio and transcripts into structured summaries, action items, and decisions, with seamless integrations to push your results directly to Notion and Slack.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages