A small Slack app for organizing Premday usergroup meetings.
/meetingopens a Slack modal with a default meeting one month from now at 18:00 Europe/Paris, converted to the creator's Slack timezone.PREMDAY_TIMEZONEis used if Slack does not provide a valid timezone.- The bot creates a Slack Canvas for each channel and meeting year, named
Premday meetings <year>, then adds later meetings from that channel and year. - The bot emails calendar invitations to channel members through SMTP.
- The bot posts a meeting message with the Proton Meet link, Canvas link, and a
Receive invitationbutton for newcomers. - Meetings are persisted in SQLite so button clicks still work after process restarts.
- RSVP replies to calendar invitations are accepted through an LMTP listener and update the meeting message with Going, Maybe, and Can't go counts.
- A
View RSVPsbutton privately shows responder names in independently expandable Going, Absent, and Maybe sections. Expanded names appear in two columns, using Slack user mentions for members and email addresses for invitees without a Slack account.
cp .env.example .envCreate a Slack app from slack/app-manifest.yaml, then set the
values in .env.
The app uses Socket Mode, so it does not need a public HTTP endpoint.
Required Slack scopes:
commandschat:writeusers:readusers:read.emailchannels:readchannels:historygroups:readim:readmpim:readcanvases:writecanvases:read
On a server with Docker Compose, create .env from .env.example, fill in the
Slack and SMTP settings, then start the bot:
cp .env.example .env
docker compose up -d --build
docker compose logs -f premday-botThe SQLite database lives in the premday_data Docker volume, so it survives
container rebuilds and restarts. Compose sets PREMDAY_DB_PATH to
/data/premday.sqlite3 inside the container. The app connects to Slack using Socket Mode;
Compose publishes the optional RSVP listener on host loopback port 2525 for the local
OpenSMTPD relay.
To receive calendar replies, configure RSVP_EMAIL_ADDRESS in .env. Docker publishes
the listener on host loopback port 2525 by default (RSVP_SMTP_HOST_PORT can change it).
Configure OpenSMTPD to deliver mail for RSVP_EMAIL_ADDRESS over LMTP to
127.0.0.1:2525. The listener only accepts that recipient and binds through a loopback-only
Docker port mapping; do not expose it directly to the Internet. PREMDAY_EMAIL_FROM should
use RSVP_EMAIL_ADDRESS, because calendar clients send replies to the organizer address.
For RSVP_EMAIL_ADDRESS=rsvp@example.com, add this action with your other actions and put
the match rule before broader recipient rules:
action "premday_rsvp" relay host lmtp://127.0.0.1:2525
match from any for rcpt-to "rsvp@example.com" action "premday_rsvp"If moving an existing installation, stop the old bot and copy its database into the volume before the first Compose start. This preserves the Canvas IDs used to append meetings to the existing Canvas:
docker compose build
docker compose run --rm --no-deps \
--volume "$PWD/premday.sqlite3:/tmp/old.sqlite3:ro" \
--entrypoint sh premday-bot \
-c 'cp /tmp/old.sqlite3 /data/premday.sqlite3'
docker compose up -dTo stop the bot, run docker compose down (leave the volume in place).
For a local run without Docker:
uv sync
uv run premday-botIn Slack:
/meeting
| Variable | Required | Description |
|---|---|---|
SLACK_BOT_TOKEN |
yes | Bot token, usually xoxb-.... |
SLACK_APP_TOKEN |
yes | Socket Mode app token, usually xapp-.... |
SLACK_TEAM_ID |
no | Team ID used to build a Canvas URL if Slack does not return one. |
PREMDAY_TIMEZONE |
no | Fallback timezone when Slack has no valid timezone for the creator; defaults to Europe/Paris. |
PREMDAY_DEFAULT_HOUR |
no | Defaults to 18. |
PREMDAY_DEFAULT_DURATION_MINUTES |
no | Defaults to 90. |
PREMDAY_DB_PATH |
no | Defaults to premday.sqlite3. |
SMTP_HOST |
yes | SMTP server hostname. |
SMTP_PORT |
no | Defaults to 587. |
SMTP_USERNAME |
no | SMTP login. |
SMTP_PASSWORD |
no | SMTP password. |
SMTP_USE_TLS |
no | STARTTLS, defaults to true. |
SMTP_USE_SSL |
no | Implicit TLS, defaults to false. |
PREMDAY_EMAIL_FROM |
yes | Sender header for invitations. |
RSVP_EMAIL_ADDRESS |
no | Address in calendar invites and only recipient accepted by the RSVP listener. |
RSVP_SMTP_HOST_PORT |
no | Host loopback port published by Compose; defaults to 2525. |
uv run ruff check .
uv run pyright
uv run pytest- Topic proposal and voting before each meeting.
- Automatic reminder messages one week and one day before the meeting.
- Speaker signup with agenda slots.
- Post-meeting recap generation from Canvas content.
- RSVP attendee names in the Slack message (currently only response counts are shown).