Self-hosted async standup tool. No Azure subscription required for core features. Runs on PHP 8.3 + SQLite (or MySQL / PostgreSQL). Delivers daily standup prompts and summaries by email or MS Teams.
| Feature | |
|---|---|
| ✅ | Team management — create, edit, suspend, delete teams |
| ✅ | Configurable standup questions per team (order, blockers flag, mood flag) |
| ✅ | Timezone-aware scheduling with configurable frequency (daily / weekdays / weekly) |
| ✅ | Standup prompt delivery — email or MS Teams Adaptive Card DM |
| ✅ | Web response form + Teams card submission via Bot Framework |
| ✅ | Submission reminders (configurable window before expiry) |
| ✅ | Blocker question flagging — |
| ✅ | Mood / sentiment tracking — 5-level score, 30-day trend graph |
| ✅ | Daily summary delivery — email or Teams channel Incoming Webhook (Adaptive Card) |
| ✅ | Response browser per team |
| ✅ | Public REST API v1 — API key auth, rate-limited (100 req/hr) |
| ✅ | MCP server — stdio transport, 6 tools, Claude Desktop + Pi SDK ready |
| ✅ | API key management UI — generate, list, revoke (soft-delete) |
| ✅ | MS Teams integration — 3 notification modes |
| ✅ | Teams admin overview — mode badges, last delivery error tracking |
- PHP 8.3+ (CLI for cron; FPM/Apache/Nginx for web)
- Database: SQLite 3 (default) · MySQL 5.7+ · PostgreSQL 13+
- SMTP: any plain relay (localhost Postfix, SendGrid, etc.)
- HTTPS: required only for Teams Bot DM webhook endpoint (
/bot/webhook)
# 1. Clone
git clone https://github.com/cedric-r/asyncstandup.git
cd asyncstandup
# 2. Copy and edit config
cp config/config.example.php config/config.php
$EDITOR config/config.php
# 3. Create the database (SQLite example)
touch asyncstandup.sqlite
php -r "require 'src/Db.php'; getDb(require 'config/config.php');"
# 4. Apply schema
sqlite3 asyncstandup.sqlite < db/schema.sql
# MySQL: mysql asyncstandup < db/schema.sql
# PostgreSQL: psql asyncstandup < db/schema-postgresql.sql
# 5. Set up cron (see Cron Jobs section)
# 6. Point your web server at public/Edit config/config.php (copy from config/config.example.php):
return [
'app_url' => 'https://standup.example.com',
'db' => [
'driver' => 'sqlite', // sqlite | mysql | pgsql
'path' => '/var/data/asyncstandup.sqlite', // SQLite only
// MySQL / PostgreSQL:
'host' => '127.0.0.1',
'port' => 3306,
'name' => 'asyncstandup',
'user' => 'root',
'pass' => '',
'charset' => 'utf8mb4',
],
'smtp' => [
'host' => 'localhost',
'port' => 25,
'from' => 'standup@example.com',
'from_name' => 'AsyncStandUp',
],
// Optional — required for notification_channel = 'teams' (Bot DM prompts)
'teams_bot' => [
'app_id' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'app_secret' => 'your-bot-app-secret',
'service_url' => 'https://smba.trafficmanager.net/emea/',
'bot_webhook_path' => '/bot/webhook',
],
];Configure per team in Team Settings → Notification Channel.
| Mode | Prompt delivery | Summary delivery | Requires |
|---|---|---|---|
email |
SMTP | ||
teams |
Teams Adaptive Card DM | Azure Bot registration + HTTPS | |
teams-summary |
Teams Incoming Webhook (Adaptive Card) | Teams Incoming Webhook URL only |
teams-summary is the easiest Teams integration — no Azure account needed, just a Webhook URL from Teams admin.
Three independent passes. Recommended schedule (adjust times to suit):
# Pass 1 — Send standup prompts at each team's configured standup_time
* * * * * php /var/www/asyncstandup/cron/send_standups.php >> /var/log/asyncstandup.log 2>&1
# Pass 2 — Send submission reminders (fires ~2 h before token expiry)
* * * * * php /var/www/asyncstandup/cron/send_standups.php reminder >> /var/log/asyncstandup.log 2>&1
# Pass 3 — Send daily summaries after standup window closes
* * * * * php /var/www/asyncstandup/cron/send_standups.php summary >> /var/log/asyncstandup.log 2>&1Base URL: https://standup.example.com/api/v1
Auth: Authorization: Bearer sk-<api-key>
Rate limit: 100 requests / hour per key
| Method | Endpoint | Description |
|---|---|---|
GET |
/teams |
List teams accessible to the authenticated user |
GET |
/teams/{id}/questions |
List standup questions for a team |
GET |
/teams/{id}/submissions |
Recent submissions (paginated) |
GET |
/submissions/{id} |
Single submission with answers |
POST |
/submissions |
Submit a standup (token + answers[]) |
Example:
curl -H "Authorization: Bearer sk-abc123" \
https://standup.example.com/api/v1/teams
curl -X POST \
-H "Authorization: Bearer sk-abc123" \
-H "Content-Type: application/json" \
-d '{"token":"tok-xyz","answers":{"1":"Finished the PR","2":"Code review today"}}' \
https://standup.example.com/api/v1/submissionsExposes 6 tools over stdio JSON-RPC 2.0 for use with AI assistants.
ASYNCSTANDUP_API_KEY=sk-<key> php mcp/server.phpTools:
| Tool | Description |
|---|---|
list_teams |
List all teams |
list_questions |
List questions for a team |
get_submissions |
Recent submissions for a team |
get_submission |
Single submission detail |
submit_standup |
Submit answers for a token |
get_team_stats |
Participation and mood statistics |
Claude Desktop (~/.config/claude/claude_desktop_config.json):
{
"mcpServers": {
"asyncstandup": {
"command": "php",
"args": ["/var/www/asyncstandup/mcp/server.php"],
"env": { "ASYNCSTANDUP_API_KEY": "sk-<key>" }
}
}
}See mcp/README.md for Pi SDK configuration.
Delivers a formatted Adaptive Card to a Teams channel after standup closes.
- In Teams: go to the channel → ⋯ More options → Connectors → Incoming Webhook
- Create a webhook, copy the URL
- In AsyncStandUp: Team Settings → Notification Channel → Teams Summary
- Paste the webhook URL into Webhook URL
- Done — summaries will be posted to the channel automatically
Delivers an interactive Adaptive Card directly to each team member's Teams DMs.
- Register an Azure Bot (free tier): Azure Portal → Bot Services
- Note the AppId and generate an App Secret (client secret)
- Add to
config/config.phpunderteams_bot - Point the bot's Messaging Endpoint to
https://your-domain.com/bot/webhook - In AsyncStandUp: Team Settings → Notification Channel → Teams DM
Note: Members must first message the bot (or be added to a conversation) to establish a conversation reference before proactive DMs can be sent. First-time users automatically fall back to email.
Security: RS256 JWT signature verification is not enabled in v1. See
public/bot/webhook.phpfor the TODO. Add JWKS verification before production deployment.
All three backends share the same schema structure. Migration files:
| File | Purpose |
|---|---|
db/schema.sql |
MySQL schema (also used as reference) |
db/schema-postgresql.sql |
PostgreSQL schema (IF NOT EXISTS, SERIAL, TIMESTAMP) |
tests/schema-sqlite.sql |
SQLite schema (used in test suite) |
php tests/phpunit.phar --configuration tests/phpunit.xml127 tests · 255 assertions · PHPStan level 5 · zero errors
Test classes cover: DB abstraction, team lifecycle, answer history, reminders, frequency, blocker flagging, mood tracking, public API, MCP server, API key management, Teams schema, Teams channel summary, Teams bot prompts, bot webhook, Teams fallback/error tracking.
| Area | Status |
|---|---|
| Bot webhook JWT | public/bot/webhook.php |
| Bot access token cache | Stored in system temp dir as JSON; chmod 0600 applied after write |
| API keys | Stored as SHA-256 hash only; raw key displayed once at creation, never again |
| Webhook serviceUrl | Validated against allowlist (smba.trafficmanager.net, webchat.botframework.com) before use — prevents Bearer token exfiltration |
| Teams error tracking | Last delivery error stored per team; visible in admin overview only |
Access at /admin/ (admin users only).
| Page | URL | Description |
|---|---|---|
| User management | /admin/users.php |
List, invite, promote users |
| Teams overview | /admin/teams.php |
All teams with mode badges and last Teams error |
MIT — see LICENSE
