Open-source alternative to Doodle, Calendly, and LettuceMeet
A modern, self-hosted polling and scheduling platform for teams. Create surveys, schedule meetings, and manage event signups with ease. Multi-language support (German & English), GDPR-compliant, accessibility-tested (WCAG 2.1 AA), and fully self-hosted.
# Clone & Start - No configuration needed!
git clone --branch v0.1.0-beta.9 --depth 1 https://github.com/manfredsteger/polly.git
cd polly
docker compose up -d
# OR: Destructive fresh local setup (deletes volumes; includes seed data)
make completeThat's it! Open http://localhost:3080 and start exploring.
| Field | Value |
|---|---|
| Username | admin |
| Password | Admin123! |
admin@polly.local |
⚠️ Security Warning: After first login, create a new admin account and delete the default admin!
- ✅ Database auto-configured (PostgreSQL included)
- ✅ Schema auto-applied on first start
- ✅ Initial admin account created automatically
- ✅ Works out of the box - no
.envfile needed - ✅ Demo data shows all three poll types
Create your first poll in under 30 seconds – no login required
Manage users, polls, and system settings from one central dashboard
📅 Schedule Poll Find the best meeting time |
📊 Survey Collect opinions & feedback |
📋 Organization List Slot booking with capacity |
🖥️ Admin Dashboard Screenshots (click to expand)
| Overview | User Management |
|---|---|
![]() |
![]() |
| System stats, recent activity, quick actions | Manage users, roles, and permissions |
| Branding & Theming | Settings |
|---|---|
![]() |
![]() |
| Customize logo, colors, and site name | Email, security, and system configuration |
| Monitoring | Test Suite |
|---|---|
![]() |
![]() |
| System health and performance | Built-in security and integration tests |
| Type | German Name | Description |
|---|---|---|
| 📅 Schedule | Terminumfrage | Find the best date/time with Yes/Maybe/No voting |
| 📊 Survey | Umfrage | Classic polls with text options and optional images |
| 📋 Organization | Orga-Liste | Slot booking with capacity limits and comments |
- Multi-Language Support: Full German (de) and English (en) interface with automatic browser detection. Easily extensible—add new languages by creating a translation JSON file in
client/src/locales/ - Anonymous & Authenticated Voting: Works for guests and registered users
- Simple Choice Response Mode: Schedule and survey polls can switch from classic Yes/Maybe/No voting to a simple single-choice (radio) or limited multiple-choice (checkbox) mode with a clear, high-contrast selected state
- Guest Access Controls: Administrators can separately enable or disable guest poll creation and guest voting without disabling public poll links
- Real-Time Updates: Live voting with WebSocket connections and fullscreen presentation mode
- Email Notifications: Vote confirmation, edit links, and expiry reminders via email
- Matrix Results View: Visual participant × options grid with color-coded responses
- Export Options: CSV and PDF exports with QR codes
- Calendar Integration: ICS export and webcal:// subscription for schedule polls
- QR Code Sharing: Easy poll distribution via QR codes
- Full Customization: Theme colors, logo, site name via admin panel
- Dark Mode: System-wide dark mode with admin defaults
- AI Poll Assistant: Create polls via natural language with AI-powered suggestions (GWDG SAIA integration)
- Voice Input: Speech-to-text for AI chat with real-time waveform visualization
- Transactional Slot Booking: PostgreSQL row-level locking prevents overbooking in organization polls
- Local Login: Email/password for registered users
- Keycloak OIDC: Enterprise SSO integration (optional)
- Role-Based Access: User, Admin, Manager roles
- Admin MFA Policy: Require MFA for administrator accounts, with an emergency deployment override for account-recovery incidents
# Clone the repository
git clone --branch v0.1.0-beta.9 --depth 1 https://github.com/manfredsteger/polly.git
cd polly
# Copy and customize environment
cp .env.example .env
nano .env # Add your SMTP, Keycloak settings
# Start with Docker Compose
docker compose up -d
# The app auto-configures on first start!# Prerequisites: Node.js 22 LTS, PostgreSQL 15+
# Clone and install
git clone --branch v0.1.0-beta.9 --depth 1 https://github.com/manfredsteger/polly.git
cd polly
npm install
# Set up environment
cp .env.example .env
# Edit .env with your database URL and settings
# Push database schema
npm run db:push
# Start development server
npm run dev
# Open http://localhost:3080Full reference: See
.env.examplefor a ready-to-use template with all variables. Production guide: Seedocs/SELF-HOSTING.mdfor deployment-specific configuration.
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection URL | postgresql://user:pass@host:5432/polly |
SESSION_SECRET |
Session encryption key (min 32 chars). Generate with openssl rand -base64 32 |
a1b2c3d4... |
| Variable | Description | Example |
|---|---|---|
APP_URL |
Public URL of your application (used for OIDC redirects, email links, QR codes, sharing) | https://poll.example.com |
Note:
BASE_URLandVITE_APP_URLare supported as legacy aliases. UseAPP_URLfor new deployments.
| Variable | Description | Default |
|---|---|---|
SMTP_HOST |
SMTP server hostname | — |
SMTP_PORT |
SMTP port | 587 |
SMTP_SECURE |
Use TLS (true/false) |
false |
SMTP_USER |
SMTP username | — |
SMTP_PASSWORD |
SMTP password | — |
FROM_EMAIL |
Sender address for outgoing emails | noreply@polly.example.com |
FROM_NAME |
Sender display name | Polly |
Legacy aliases:
EMAIL_FROM(same asFROM_EMAIL),SMTP_PASS(same asSMTP_PASSWORD)
| Variable | Description | Example |
|---|---|---|
KEYCLOAK_REALM |
Keycloak realm name | university |
KEYCLOAK_CLIENT_ID |
Client ID | polly |
KEYCLOAK_CLIENT_SECRET |
Client secret | secret-uuid |
KEYCLOAK_AUTH_SERVER_URL |
Keycloak base URL | https://keycloak.example.com |
KEYCLOAK_ISSUER_URL |
Full OIDC issuer URL (auto-derived if not set) | https://keycloak.example.com/realms/myrealm |
KEYCLOAK_ADMIN_CLIENT_ID |
Client ID for Keycloak Admin API calls (e.g. email-exists check). Falls back to KEYCLOAK_CLIENT_ID |
— |
KEYCLOAK_ADMIN_CLIENT_SECRET |
Client secret for Keycloak Admin API calls. Falls back to KEYCLOAK_CLIENT_SECRET |
— |
SSO_BUTTON_LABEL |
Custom login button text (e.g. "Kita Hub Login"). Also configurable in Admin panel | — |
HIDE_LOGIN_FORM |
Hide local username+password login form when SSO is primary | false |
Legacy alias:
KEYCLOAK_URL(same asKEYCLOAK_AUTH_SERVER_URL)
| Variable | Description | Default |
|---|---|---|
AI_API_URL |
OpenAI-compatible API endpoint | — |
AI_API_KEY |
API key. When set via ENV, AI chat is auto-enabled without admin toggle | — |
AI_API_KEY_FALLBACK |
Fallback key (used on HTTP 429 rate limit) | — |
AI_MODEL |
AI model name | llama-3.3-70b-instruct |
| Variable | Description | Default |
|---|---|---|
CLAMAV_ENABLED |
Enable ClamAV virus scanning for uploads | false |
CLAMAV_HOST |
ClamAV daemon hostname | clamav |
CLAMAV_PORT |
ClamAV daemon port | 3310 |
PENTEST_TOOLS_API_TOKEN |
Pentest-Tools.com Pro API token | — |
When CLAMAV_ENABLED, CLAMAV_HOST, or CLAMAV_PORT is explicitly set, it overrides the persisted Admin → Security scanner configuration at runtime. Leave all three unset to manage ClamAV in the Admin Panel.
These values can also be edited from the Admin Panel after first start. When set via ENV, the corresponding form field becomes read-only (lock icon).
| Variable | Description | Example |
|---|---|---|
SITE_NAME |
Site name shown in the navbar/title | Poll |
SITE_NAME_ACCENT |
Accented part of the site name (highlighted letter) | y |
FAVICON_URL |
Public URL of the favicon (PNG/ICO/SVG) | https://example.com/favicon.png |
LOGO_URL |
Public URL of the site logo | https://example.com/logo.png |
PRIMARY_COLOR |
Primary brand colour (hex) | #F97316 |
POLLY_COPYRIGHT_TEXT |
Footer copyright text. Limited HTML supported (links, basic markup). When set, the admin field is locked. | © 2026 My Org |
MFA_ADMIN_REQUIRED |
Emergency override: set to false to temporarily bypass the admin MFA setting during an account-recovery incident. Remove or leave unset to use the admin-panel setting. |
— |
| Variable | Description | Default |
|---|---|---|
ADMIN_USERNAME |
Initial admin username | admin |
ADMIN_EMAIL |
Initial admin email | admin@polly.local |
ADMIN_PASSWORD |
Initial admin password | Admin123! |
SEED_DEMO_DATA |
Seed demo polls on first start | false |
DOCKER_ENV |
Marker that the app runs inside the bundled Docker image (auto-set by docker-compose.yml) |
true (in Docker) |
POSTGRES_USER |
Bundled PostgreSQL user (docker-compose only) | polly |
POSTGRES_PASSWORD |
Bundled PostgreSQL password (docker-compose only) | polly_secret |
POSTGRES_DB |
Bundled PostgreSQL database (docker-compose only) | polly |
These values create a missing admin only; they never overwrite an existing account on restart. Database-backed tests require a separately initialized test database. Normal application operation does not require one. See TESTING.md.
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 5000 |
NODE_ENV |
Node environment | production |
DATABASE_SSL |
Enable SSL for database connections | false |
FORCE_HTTPS |
Force secure cookies (behind TLS-terminating proxy) | auto-detect from APP_URL |
DOCKER |
Legacy runtime marker used by some deployments to indicate container execution | true |
LOG_LEVEL |
Logging level: debug, info, warn, error |
info (prod) / debug (dev) |
PUPPETEER_EXECUTABLE_PATH |
Chromium path for PDF export | auto-detected |
POLLY_WCAG_OVERRIDE |
Disable WCAG default theme enforcement | false |
TEST_MODE_SECRET |
Custom header value for E2E test mode | polly-e2e-test-mode |
TEST_DATABASE_URL |
Separate database for automated tests; see Testing Guide | — |
TEST_DATABASE_SSL |
SSL option for the test database connection | false |
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite |
| UI | Shadcn/ui, Radix UI, Tailwind CSS |
| State | TanStack Query v5 |
| Backend | Express.js, TypeScript |
| Database | PostgreSQL, Drizzle ORM |
| Auth | bcrypt + express-session (local), openid-client (Keycloak OIDC) |
| AI | GWDG KISSKI / SAIA (OpenAI-compatible), Whisper for speech-to-text |
├── client/ # React frontend (Vite)
│ └── src/
│ ├── components/ # Reusable UI + admin panels
│ ├── pages/ # Route components (wouter)
│ ├── hooks/ # Custom React hooks
│ ├── contexts/ # Auth, theme, language providers
│ ├── lib/ # queryClient, helpers
│ ├── locales/ # i18n JSON (de, en)
│ └── assets/ # Bundled images & logos
├── server/ # Express backend
│ ├── index.ts # App bootstrap, session, security headers
│ ├── routes/ # Modular route files
│ │ ├── index.ts # Mount all routers under /api/v1
│ │ ├── auth.ts # Local login, register, password reset
│ │ ├── polls.ts # Poll CRUD & voting
│ │ ├── votes.ts # Vote management
│ │ ├── users.ts # User self-service endpoints
│ │ ├── admin.ts # Admin panel API (settings, users, polls)
│ │ ├── ai.ts # AI chat / transcription proxy
│ │ ├── export.ts # CSV / PDF / ICS export
│ │ ├── system.ts # Public branding / health
│ │ └── common.ts # Shared middleware
│ ├── services/ # authService, emailService, AI, rate limiter
│ ├── scripts/ # ensureSchema, applyBranding bootstrap
│ ├── seed-admin.ts # Initial admin seeder
│ ├── storage.ts # Drizzle data-access layer
│ └── tests/ # Vitest unit / integration / api / e2e
├── shared/ # Code shared by client + server
│ ├── schema.ts # Drizzle schema + Zod insert schemas
│ └── servicePartners.ts # Single source of truth for GWDG/KISSKI
├── docs/ # API spec, architecture, self-hosting guide
├── Dockerfile # Production container (multi-stage)
└── docker-compose.yml # Zero-config production setup
npm run dev # Start development server (frontend + backend)
npm run build # Build for production
npm start # Start production server
npm run db:push # Push schema changes to database
npm run db:studio # Open Drizzle Studio (DB viewer)Access the admin panel at /admin to customize:
- Theme Colors: Primary, secondary, and feature-specific colors
- Branding: Logo, site name, footer text
- Dark Mode: Set system default (light/dark/system)
- Registration: Enable/disable user registration
- Email Settings: Configure SMTP for notifications
Polly ships as an installable Progressive Web App. Chrome, Edge, and Safari/iOS users can add it to their homescreen for a native-feeling launcher and offline fallback page. The web manifest at /site.webmanifest is rendered dynamically from your admin settings, so the homescreen icon name, splash-screen background, and accent color reflect the siteName, default theme mode, and primary color you configure in the admin panel — no rebuild needed. A service worker caches the app shell and shows a friendly offline page when the network is unreachable.
| Feature | Default Color | Description |
|---|---|---|
| Schedule | Orange #F97316 | Terminumfragen |
| Survey | Green #7DB942 | Umfragen |
| Organization | Teal #72BEB7 | Orga-Listen |
- Session-based authentication with secure cookies
- Server-side validation of all inputs with Zod
- Email ownership verification for registered users
- Role-based access control
- CSRF protection
- Secure password hashing with bcrypt
- AI API key proxying (server-side only)
# Show all available commands
make help
# Quick setup (first time)
make setup
# Start production
make prod
# Start development with hot-reload
make dev
# View logs
make logs
# Push database schema
make db-push
# Open database shell
make shell-db
# Destructive fresh local setup: deletes volumes, rebuilds and seeds
make complete
# Build and publish to Docker Hub
make publish IMAGE_NAME=yourusername/polly# Build and run
docker-compose up -d --build
# View logs
docker-compose logs -f app
# Stop
docker-compose downdocker-compose -f docker-compose.yml -f docker-compose.dev.yml upThe official image is published as manfredsteger/polly on Docker Hub:
# Latest beta tag (evaluation)
docker pull manfredsteger/polly:beta
# Specific version (recommended for production)
docker pull manfredsteger/polly:0.1.0-beta.9
# Start the pinned image with PostgreSQL (Portainer/Synology compatible)
docker compose -f docker-compose.image.yml up -dSee docs/SELF-HOSTING.md for production deployment instructions.
All API endpoints use the /api/v1/ prefix. Full OpenAPI specification available in docs/openapi.yaml.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/polls/public/:token |
Get poll by public token |
| POST | /api/v1/polls/:publicToken/vote |
Submit vote |
| GET | /api/v1/polls/:token/results |
Get poll results |
| GET | /api/v1/polls/:publicToken/calendar.ics |
ICS calendar export |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/polls |
Create poll |
| PATCH | /api/v1/polls/:id |
Update poll |
| DELETE | /api/v1/polls/:id |
Delete poll |
| GET | /api/v1/users/me |
Get current user |
| PATCH | /api/v1/users/me/language |
Update language preference |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/admin/polls |
List all polls |
| GET | /api/v1/admin/users |
List all users |
| PUT | /api/v1/admin/settings |
Update settings |
| GET | /api/v1/admin/email-templates |
Manage email templates |
Connect to /ws for live vote updates during presentations. Events: vote_update, slot_update.
Polly is currently in Beta Phase (Q1 2025 – Q2 2026). Our focus areas:
| Priority | Feature | Status |
|---|---|---|
| 🤖 | AI Voice & Agentic Poll Creation – GWDG KISSKI Free Tier, Whisper STT | ✅ Released (beta.3) |
| 🗓️ | Schedule Poll Enhancements – video conf URL, ICS labels, finalize UX | ✅ Released (beta.3) |
| ✉️ | Notifications – End-Poll mails for all poll types, voter cancellation | ✅ Released (beta.3) |
| 🛡️ | Guest Access & Admin MFA – separate guest policies plus a recoverable MFA requirement | ✅ Released (beta.3) |
| 🐳 | Docker Zero-Config – fixed plain-HTTP cookie regression | ✅ Released (beta.3) |
| 🔐 | Keycloak SSO (OIDC) – Enterprise single sign-on, full E2E coverage | In Progress |
| 🔌 | OpenAI-Compatible Provider Slots – swap in custom inference endpoints | Planned (1.0) |
| 💬 | Matrix / Element Chatbot – manage polls from Matrix chat | Planned (1.0) |
| 🇪🇺 | European DC Focus – simplified deployment for EU data centers | Planned (1.0) |
|
|
GWDG – Gesellschaft für wissenschaftliche Datenverarbeitung mbH Göttingen The GWDG is the joint computing and IT competence center for the Georg-August-Universität Göttingen and the Max Planck Society. As infrastructure partner, they provide the hosting and computing resources that power Polly's self-hosted deployment capabilities. |
|
|
KISSKI – KI-Servicezentrum für sensible und kritische Infrastrukturen KISSKI is a BMBF-funded AI service center operated by the GWDG, providing free AI inference capabilities for research and public infrastructure. Polly uses KISSKI's OpenAI-compatible API to offer AI-powered agentic control for poll creation – hosted in Germany, GDPR-compliant, and offers a free AI tier for every Polly user (please get in touch with GWDG/KISSKI for an AI production licence). |
| Document | Description |
|---|---|
| ROADMAP.md | Development roadmap and future plans |
| CHANGELOG.md | Version history and release notes |
| SECURITY.md | Security policy and vulnerability reporting |
| docs/openapi.yaml | OpenAPI 3.0 API specification |
| docs/SELF-HOSTING.md | Production deployment guide (Docker, Portainer, Synology, reverse proxy) |
| docker-compose.image.yml | Pinned public Docker image with PostgreSQL |
| docker-compose.image.external-db.yml | Pinned public Docker image for an external PostgreSQL database |
| docs/FLUTTER_INTEGRATION.md | Mobile app integration guide |
We welcome contributions! See CONTRIBUTING.md for the full guide.
Current beta branch:
feature/guest-access-control– source branch for the current public beta and its release tagmain– upstream stable branch; it is not changed as part of this beta releasefeature/<name>– topic branches for future work
Quick steps:
- Fork the repository and clone your fork
- Create a feature branch from the current beta branch:
git checkout -b feature/amazing-feature origin/feature/guest-access-control - Commit your changes (
git commit -m 'Add amazing feature') - Push to your fork (
git push origin feature/amazing-feature) - Open a Pull Request against the branch agreed for the next release
- Follow existing code style and conventions
- Write TypeScript with proper types
- Use Tailwind CSS for styling
- Add or extend tests for every behaviour change (see CONTRIBUTING.md)
- Update documentation (
README.md,CHANGELOG.md,.env.example) as needed
This project is open source and available under the MIT License.
- Built with Shadcn/ui and Radix UI
- Icons by Lucide
- AI services provided by GWDG KISSKI (free tier for self-hosters)
- Hosting infrastructure for the reference deployment by GWDG
- Self-hostable on any Docker-capable host – see docs/SELF-HOSTING.md
- Originally prototyped on Replit
Made with ❤️ for teams everywhere – self-hosted, GDPR-friendly, open source.














