A self-hosted Slack bot for meeting scheduling polls, event management with RSVPs, automated new member onboarding, and member re-engagement. Uses Socket Mode (no public URL required) and SQLite for storage. Perfect for Raspberry Pi deployment.
/meetpollslash command to create polls- Support for 5-25 time slot options
- Checkbox-based multi-select voting
- Real-time vote counting with transparency (shows who voted)
- Manual or automatic poll closing
- Detailed results view
/event createto create events with a modal form/event listto see upcoming events- Going / Maybe / Not Going RSVP buttons
- Optional max attendee limit (rejects Going when full)
- Automatic 24h and 1h reminders via DM to RSVPed users
- Auto-close events after their scheduled time
- Periodically checks a Google Sheet registration form for new entries
- Sends bilingual (Turkish/English) welcome emails with Slack invite link
- On
team_join, auto-adds members to their selected committee channels - Sends bilingual welcome DM with committee info, upcoming events, and community channels guide
- When a new member joins a committee channel, sends a DM to that committee's leader (Turkish, with vowel harmony)
/onboardcommand for managing the system (status, mappings, leaders, manual runs)- First-run safety:
/onboard seedto import existing members without emailing them
- Syncs upcoming events from a Google Calendar into the local database every 6 hours
- Welcome DMs and nudge messages automatically reference the next upcoming event
- Enable by setting
GOOGLE_CALENDAR_IDin.envand enabling the Google Calendar API in your Cloud project
- Scores inactive Slack members by education level, membership type, committee interest, and recency
/engage review— sends 5 ranked nudge candidates to admin DM as review cards- Each card shows member profile, draft bilingual message (Turkish first, then English), and four actions:
- Send — DMs the member immediately (with confirm dialog)
- Edit & Send — opens a modal to edit the message before sending
- Skip 30d — puts member on a 30-day cooldown
- Dismiss — permanently removes member from nudge suggestions
/engage dm @user— manually target any specific member by @mention or name- Nudge messages are bilingual, informal "sen" tone, reference committee leaders by @mention, and include dynamic admin contact lines pulled from the DB
- Scheduler sends a review batch to the admin automatically (configurable interval)
- Full audit trail: every sent nudge is logged in the message log
/outreach academics— compose personalized emails to academic contacts from a Google Sheet/outreach clubs— compose personalized emails to student clubs from a Google Sheet- Auto-prepended greetings: "Sayın {Ad Soyad} Hocam," for academics, "Sevgili {Kulüp Adı}," for clubs
- Preview with 3 sample emails before confirming send
- Rate-limited background sending (2.5s between emails, Pi-friendly)
- Resumable campaigns — each recipient tracked individually
/outreach status— aggregate statistics/outreach history— recent campaigns with expandable details
- When a new member is onboarded, automatically adds them to a Google Group via Admin SDK
- Requires domain-wide delegation (DWD) configured in Google Workspace Admin Console
- Idempotent — members already in the group are silently skipped
- Retry logic: members who missed group-add (e.g. during downtime) are retried on next registration check
- If
GOOGLE_GROUP_EMAILis not set, this feature is silently skipped
- Fetches multiple sources twice daily (10:00 and 22:00) and on demand via
/queue scan - Sources:
- jobrxiv.org — bioinformatics-specific job listings
- opportunitydesk.org — fellowships, scholarships, grants
- ELIXIR TeSS — European training events, summer schools, workshops
- EMBL and Wellcome Sanger Institute via Workday API
- Filters by bioinformatics keywords in the title (not just description — avoids false positives)
- Title blacklist rejects senior/postdoc/director-level roles automatically
- Entries older than 30 days are skipped to avoid expired listings
- Adzuna API support for additional European internship search (optional, requires free API key)
- New items are queued and posted at random times within the 10:00–22:00 window
- Maximum 5 posts per calendar day, preventing channel spam
- Each posted item is tracked by GUID — never posted twice, never re-queued after manual deletion
- Posts to the channel configured via
JOBS_CHANNEL_ID /queuecommand lets admins view, delete, and manually scan the pending queue
TUBITAK note: TUBITAK does not provide an RSS feed for scholarship/call announcements. Monitor tubitak.gov.tr/en/announcements manually for BIDEB calls (2205, 2209, 2247-C).
- Python 3.8+
- A Slack workspace where you have admin permissions
- A Google Cloud project with Sheets API enabled (free, no billing required)
- A Gmail account with 2-Step Verification and an App Password
- Go to https://api.slack.com/apps
- Click "Create New App"
- Choose "From scratch"
- Enter app name:
MeetPoll - Select your workspace
- Click "Create App"
Socket Mode allows your bot to connect without a public URL.
- In the left sidebar, click "Socket Mode"
- Toggle "Enable Socket Mode" to ON
- When prompted, create an App-Level Token:
- Token Name:
meetpoll-socket - Scope:
connections:write
- Token Name:
- Click "Generate"
- Copy and save the token (starts with
xapp-) - you'll need this later
- In the left sidebar, click "OAuth & Permissions"
- Scroll to "Scopes" section
- Under "Bot Token Scopes", add these scopes:
commands- For slash commandschat:write- To post messageschat:write.public- To post in channels the bot hasn't joinedusers:read- To read user profilesusers:read.email- To match new members by emailchannels:manage- To invite users to public channelsgroups:write- To invite users to private channelsim:write- To send welcome DMs
- In the left sidebar, click "Slash Commands"
- Create three commands:
| Command | Short Description | Usage Hint |
|---|---|---|
/meetpoll |
Create a meeting scheduling poll | (opens poll creation dialog) |
/event |
Create and manage events | create or list |
/onboard |
Manage member onboarding | status, list, map, unmap, run, seed |
/outreach |
Send personalized outreach emails | academics, clubs, status, history |
/queue |
Manage the pending opportunities queue | (no args) or scan |
/engage |
Member engagement tools | stats, inactive, review, dm @user, digest, log |
/botstatus |
Show bot and scheduler status | (no args) |
/help |
Show all available commands | (no args) |
/test-welcome |
Send yourself a test welcome DM | (admin only) |
- In the left sidebar, click "Interactivity & Shortcuts"
- Toggle "Interactivity" to ON
- You don't need a Request URL with Socket Mode - leave it blank or enter a placeholder
- Click "Save Changes"
- In the left sidebar, click "Event Subscriptions"
- Toggle "Enable Events" to ON
- Under "Subscribe to bot events", add:
team_join- Triggers when a new member joins the workspace
- Click "Save Changes"
- In the left sidebar, click "Install App"
- Click "Install to Workspace"
- Review the permissions and click "Allow"
- Copy the "Bot User OAuth Token" (starts with
xoxb-)
Note: You must reinstall the app every time you add new scopes or event subscriptions.
The bot must be a member of any channel it needs to invite users to. For each committee channel:
- Open the channel in Slack
- Click the channel name at the top
- Go to the Integrations tab
- Click Add apps and add MeetPoll
The bot reads new member registrations from a Google Sheet (linked to a Google Form).
- Go to https://console.cloud.google.com
- Click "Select a project" at the top, then "New Project"
- Name it (e.g.,
meetpoll-bot) and click "Create" - No billing is required for this setup
- Go to APIs & Services > Library
- Search for "Google Sheets API"
- Click it and click "Enable"
- Go to APIs & Services > Credentials
- Click "Create Credentials" > "Service Account"
- Name it (e.g.,
meetpoll-sheets) and click "Create and Continue" - Skip the optional role/access steps and click "Done"
- Click on the service account you just created
- Go to the Keys tab
- Click "Add Key" > "Create new key" > JSON > "Create"
- A
.jsonfile will download — save it asservice_account.jsonin your project directory
- Open your Google Sheet (the one linked to your registration form)
- Click Share in the top right
- Paste the service account email address (looks like
meetpoll-sheets@your-project.iam.gserviceaccount.com— find it under IAM & Admin > Service Accounts) - Set role to Viewer and click Send
- Sheet ID: From the Google Sheet URL — the long string between
/d/and/edit:https://docs.google.com/spreadsheets/d/THIS_IS_THE_SHEET_ID/edit#gid=0 - Sheet Name: The tab name at the bottom of the sheet (e.g.,
Form Responses 1orForm Yanıtları 1)
This feature requires a Google Workspace account (not a personal @gmail.com). If you only have a personal Gmail account, skip this section and leave GOOGLE_GROUP_EMAIL empty.
- Go to https://console.cloud.google.com → your project
- Go to APIs & Services > Library
- Search for "Admin SDK API" and enable it
- Go to Google Workspace Admin Console → admin.google.com
- Navigate to Security → API Controls → Domain-wide Delegation
- Click Add new
- Enter your service account's Client ID (found in the service account JSON under
client_id) - Add scope:
https://www.googleapis.com/auth/admin.directory.group.member - Click Authorize
GOOGLE_GROUP_EMAIL=members@yourdomain.org # The Google Group to add members to
GOOGLE_ADMIN_EMAIL=admin@yourdomain.org # A Workspace admin email to impersonateThe bot sends welcome emails via Gmail SMTP using an App Password.
- Go to https://myaccount.google.com/signinoptions/two-step-verification
- Turn on 2-Step Verification and set up a verification method (phone, authenticator app, etc.)
Note: If you're using a Google Workspace account and can't enable 2-Step Verification (admin restriction), create a free personal
@gmail.comaccount for the bot instead.
- Go to https://myaccount.google.com/apppasswords
- Enter an app name (e.g.,
MeetPoll Bot) - Click "Create"
- Copy the 16-character password that appears
# Clone or copy files to your machine
cd /path/to/meetpoll
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Copy and configure environment file
cp .env.template .envEdit .env with all your credentials (see Configuring .env below).
Run the bot:
python bot.pyCopy the template and fill in all values:
cp .env.template .env# Slack Bot Tokens (from Part 1)
SLACK_BOT_TOKEN=xoxb-your-bot-token-here
SLACK_APP_TOKEN=xapp-your-app-token-here
# Database path (defaults to ./meetpoll.db)
DATABASE_PATH=./meetpoll.db
# Google Sheets (from Part 2)
GOOGLE_SERVICE_ACCOUNT_PATH=./service_account.json
GOOGLE_SHEET_ID=your-google-sheet-id-here
GOOGLE_SHEET_NAME=Form Responses 1
# Gmail SMTP (from Part 3)
GMAIL_SENDER_ADDRESS=your-email@gmail.com
GMAIL_APP_PASSWORD=abcd efgh ijkl mnop
# Onboarding
SLACK_INVITE_LINK=https://join.slack.com/t/your-workspace/shared_invite/xxx
WELCOME_METHOD=email
ONBOARD_AFTER_DATE=| Variable | Description |
|---|---|
SLACK_BOT_TOKEN |
Bot User OAuth Token from Slack app settings (starts with xoxb-) |
SLACK_APP_TOKEN |
App-Level Token for Socket Mode (starts with xapp-) |
DATABASE_PATH |
Path to SQLite database file (created automatically) |
GOOGLE_SERVICE_ACCOUNT_PATH |
Path to the service account JSON key file |
GOOGLE_SHEET_ID |
The ID from your Google Sheet URL |
GOOGLE_SHEET_NAME |
The sheet tab name (e.g., Form Responses 1) |
GMAIL_SENDER_ADDRESS |
Gmail address used to send welcome emails |
GMAIL_APP_PASSWORD |
16-character Gmail App Password |
SLACK_INVITE_LINK |
Workspace invite link (get it from Slack: workspace menu > Invite people) |
CALENDAR_LINK |
Optional Google Calendar link for event calendar integration (leave empty to skip) |
WELCOME_METHOD |
email (send email only), slack_dm (DM only), or both |
ONBOARD_AFTER_DATE |
Optional cutoff date (e.g., 2026-02-01). Entries before this date are ignored. Leave empty to process all. |
ONBOARD_SUPER_ADMIN |
Your Slack Member ID (get it from your Slack profile > "..." > Copy member ID). This user can manage onboard admins and cannot be removed. |
OUTREACH_ACADEMICS_SHEET_ID |
Google Sheet ID for academic contacts |
OUTREACH_ACADEMICS_SHEET_NAME |
Sheet tab name (default: Sheet1) |
OUTREACH_CLUBS_SHEET_ID |
Google Sheet ID for student club contacts |
OUTREACH_CLUBS_SHEET_NAME |
Sheet tab name (default: Sheet1) |
GOOGLE_GROUP_EMAIL |
Google Group email to auto-add new members to (e.g. members@yourdomain.org). Requires DWD. Leave empty to disable. |
GOOGLE_ADMIN_EMAIL |
A Google Workspace admin email to impersonate for domain-wide delegation |
JOBS_CHANNEL_ID |
Slack channel ID where bioinformatics RSS opportunities are posted (e.g. CQ14TLAGK) |
ADZUNA_APP_ID |
(Optional) Adzuna API app ID — free tier at developer.adzuna.com |
ADZUNA_APP_KEY |
(Optional) Adzuna API key — enables additional European internship search |
GOOGLE_CALENDAR_ID |
(Optional) Google Calendar ID to sync events from (e.g. abc123@group.calendar.google.com). Enable Calendar API in your Cloud project first. |
GENERAL_CHANNEL_ID |
Slack channel ID for the #general channel (used for weekly digest posts) |
ENGAGEMENT_ENABLED |
Set to true to enable the re-engagement nudge scheduler (default: true) |
# SSH into your Raspberry Pi
ssh pi@raspberrypi.local
# Install Python 3 and pip if needed
sudo apt update
sudo apt install python3 python3-pip python3-venv -y
# Create project directory
mkdir -p ~/meetpoll
cd ~/meetpoll
# Copy project files (from your local machine)
# scp bot.py database.py blocks.py sheets.py mailer.py requirements.txt .env service_account.json pi@raspberrypi.local:~/meetpoll/
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt# Copy service file
sudo cp meetpoll.service /etc/systemd/system/slackbot.service
# Adjust the service file if your username isn't 'pi'
sudo nano /etc/systemd/system/slackbot.service
# Reload systemd and enable service
sudo systemctl daemon-reload
sudo systemctl enable slackbot
sudo systemctl start slackbot
# Check status
sudo systemctl status slackbot
# View logs
sudo journalctl -u slackbot -fFrom your local machine:
scp bot.py database.py blocks.py sheets.py mailer.py google_groups.py rss_feed.py job_fetcher.py engagement.py requirements.txt pi@raspberrypi.local:~/meetpoll/Then on the Pi (use the restart-bot script to avoid duplicate processes):
sudo restart-botTo install the restart-bot helper (one-time setup):
sudo cp restart-bot /usr/local/bin/restart-bot
sudo chmod +x /usr/local/bin/restart-botsudo restart-bot # Cleanly restart the bot (recommended)
sudo systemctl stop slackbot # Stop the bot
sudo journalctl -u slackbot -n 50 # View recent logs
sudo journalctl -u slackbot -f # View live logspython bot.pyYou should see:
Scheduler started (polls, registrations, events)
MeetPoll bot starting in Socket Mode...
Bolt app is running!
If your Google Sheet already has registrations, run this in Slack before anything else to prevent sending welcome emails to existing members:
/onboard seed
This imports all current entries as already-onboarded. No emails will be sent to them.
Map committee names to Slack channels. In Slack:
/onboard map "Journal Club" #journal-club
/onboard map "Webinar" #webinar
/onboard map "Website" #website
Repeat for each committee. Verify with:
/onboard list
Assign a leader to each committee. When a new member joins their channel, the leader receives a DM:
/onboard leader set "Website" @emre
/onboard leader set "Journal Club" @ayse
Verify with:
/onboard leader list
/onboard status
Should show your seeded member count as fully_onboarded.
- Add a test entry to your Google Form with your own email
- Run
/onboard runin Slack (or wait up to 1 hour for automatic check) - Check your email for the welcome message
- Join the workspace with the link and verify you're added to the correct channels
- In any Slack channel, type
/meetpoll - A modal dialog will open with these fields:
- Poll Question: e.g., "When should we have our weekly sync?"
- Time Options: Enter one time slot per line (5-25 options required)
- Close Date/Time: Optional auto-close deadline
- Click "Create Poll"
Voting:
- Click the checkboxes next to times that work for you
- You can select multiple options
- Votes are updated in real-time with full transparency
/event create— Opens a modal to create an event (title, date, time, location, description, max attendees)/event list— Shows upcoming events
RSVP buttons appear on the event message: Going, Maybe, Not Going. If a max attendee limit is set, Going is blocked when full. Reminders are sent via DM 24 hours and 1 hour before the event.
Access Control:
/onboardand/outreachcommands are restricted to authorized admins only. SetONBOARD_SUPER_ADMINin.envwith your Slack Member ID, then use/onboard admin add @userto grant access to others.
| Command | Description |
|---|---|
/onboard status |
Show onboarding statistics |
/onboard list |
Show committee-to-channel mappings (with leaders) |
/onboard map "Committee" #channel |
Add or update a mapping |
/onboard unmap "Committee" |
Remove a mapping |
/onboard leader list |
Show all committee leaders |
/onboard leader set "Committee" @user |
Set a committee leader |
/onboard leader remove "Committee" |
Remove a committee leader |
/onboard run |
Manually check Google Sheet for new registrations |
/onboard seed |
Import all existing entries as already-onboarded (first-run safety) |
/onboard resend-since 2025-11-01 |
Re-send welcome emails to seeded members registered after a date |
/onboard user@example.com |
Send a welcome email to a specific address |
/onboard admin list |
Show all onboard admins |
/onboard admin add @user |
Add an onboard admin (super admin only) |
/onboard admin remove @user |
Remove an onboard admin (super admin only) |
| Command | Description |
|---|---|
/outreach academics |
Compose and send personalized emails to academic contacts |
/outreach clubs |
Compose and send personalized emails to student clubs |
/outreach status |
Show aggregate outreach statistics |
/outreach history |
Show recent campaigns with expandable details |
/outreach send <id> email1, email2 |
Resend a campaign to specific email addresses |
Outreach flow:
- Run
/outreach academics(orclubs) — a compose modal opens - Enter subject and body — the greeting is auto-prepended per recipient
- Click Preview — see 3 sample emails with personalized greetings
- Click Confirm Send — emails are sent in the background with 2.5s rate limiting
- Progress updates are posted to the channel every 10 emails
Manual resend: Use /outreach send <campaign_id> email1@x.com, email2@x.com to resend a past campaign to specific addresses. Accepts comma-separated or space-separated emails. Greetings are looked up from the Google Sheet automatically.
Google Sheets setup:
- Academics sheet columns:
Ünvan,Ad Soyad(or separateAd/Soyad),E-posta,Üniversite - Clubs sheet columns:
Üniversite,Kulüp Adı,İletişim E-postası,Instagram / Sosyal Medya,Alan,Notlar
Share each sheet with the service account email as Viewer. Sheets must be native Google Sheets (not uploaded .xlsx files).
| Command | Description |
|---|---|
/engage stats |
Show active / semi-active / inactive breakdown across all Slack members |
/engage inactive |
List all members inactive for 30+ days (including never-interacted) |
/engage review |
Send a batch of 5 nudge candidates to your DM for review |
/engage dm @user |
Manually target a specific member — sends a review card to your DM |
/engage digest |
Manually trigger the weekly community digest post |
/engage log |
Show last 30 days of sent DMs and nudges |
/engage log 7 |
Show last N days |
Review card actions:
- Send — DMs the draft message to the member immediately
- Edit & Send — opens a modal to edit the message before sending
- Skip 30d — puts the member on a 30-day cooldown (won't appear in suggestions)
- Dismiss — permanently removes the member from nudge suggestions
The /queue command is restricted to onboard admins.
| Command | Description |
|---|---|
/queue |
Show all pending items with a Delete button on each |
/queue scan |
Immediately scan all sources and queue new items, then report how many were added |
Deleting an item permanently suppresses it — it will never be re-queued by future scans.
| Job | Schedule | Description |
|---|---|---|
| Registration check | Every 1 hour | Checks Google Sheet for new entries, sends welcome emails, retries Google Group adds |
| Event reminders | Every 5 minutes | Sends 24h/1h reminder DMs to RSVPed users |
| Past event closer | Every 10 minutes | Auto-closes events after their scheduled time |
| Poll closer | Every 1 minute | Auto-closes polls past their deadline |
| RSS queue refresh | 10:00 and 22:00 daily | Fetches bioinformatics opportunity feeds, queues new items |
| RSS opportunity post | Random, 10:00–22:00 | Posts one queued item at a time, max 5 per day |
| Google Calendar sync | Every 6 hours + startup | Syncs upcoming events from Google Calendar into local DB |
| Engagement nudge review | Weekly (Monday 10:00) | Sends a batch of 5 nudge candidates to admin DM for review |
meetpoll/
├── bot.py # Main bot application (commands, handlers, scheduler)
├── database.py # SQLite database operations
├── blocks.py # Slack Block Kit UI builders
├── sheets.py # Google Sheets API client + Google Calendar sync
├── engagement.py # Member scoring, candidate selection, nudge message drafting
├── mailer.py # Gmail SMTP email sender
├── google_groups.py # Google Groups auto-add via Admin SDK DWD
├── rss_feed.py # RSS feed fetcher and bioinformatics keyword filter
├── job_fetcher.py # Workday and Adzuna API job fetchers
├── requirements.txt # Python dependencies
├── .env.template # Environment variables template
├── .env # Your actual environment file (do not commit)
├── service_account.json # Google service account key (do not commit)
├── meetpoll.service # Systemd service file for Raspberry Pi
├── meetpoll.db # SQLite database (created automatically)
└── README.md # This file
- Check the bot is running:
sudo systemctl status meetpoll - Verify tokens in
.envare correct - Make sure the app is installed/reinstalled after adding scopes
- Check Socket Mode is enabled in app settings
The bot must be a member of each committee channel before it can invite users. Add the bot to the channel via Slack: Channel settings > Integrations > Add apps.
Invite the bot to the channel: /invite @MeetPoll
- Verify
GOOGLE_SHEET_IDandGOOGLE_SHEET_NAMEin.env - Make sure you shared the Google Sheet with the service account email (Viewer access)
- Check the service account JSON path is correct
- Verify
GMAIL_SENDER_ADDRESSandGMAIL_APP_PASSWORDin.env - Make sure 2-Step Verification is enabled on the Gmail account
- Check that
SLACK_INVITE_LINKis set - Check logs:
sudo journalctl -u meetpoll --since "1 hour ago"
- Check database permissions:
ls -la meetpoll.db - View logs:
sudo journalctl -u meetpoll -n 100
Slack's free plan invite links expire every 30 days. When the link expires, new members won't receive a working invite in their welcome email.
Renew the link monthly — see MAINTENANCE.md for step-by-step instructions.
- Never commit
.envorservice_account.json— both are in.gitignore - The Gmail App Password grants email-sending access — keep it secret
- The service account only has read-only access to the Google Sheet
- Database file contains user IDs and emails but no authentication credentials
MIT License - Use freely for your team!