A serverless Telegram bot that provides Muslim prayer times and sends notifications when prayers are approaching.
The worldwide global bot is the flagship, actively developed version and the focus of future work. It serves prayer times anywhere from a shared location and adds a Telegram Mini App, Qibla direction, Gregorian--Hijri calendar, Islamic occasions, a rolling calendar feed, and configurable reminders. It has its own engineering and architecture guide covering the runtime, Mini App, reminder pipeline, database schemas, and GCP deployment. Its prayer calculations, high-latitude rules, Qibla bearing, Gregorian--Hijri conversion, Islamic occasions, and rolling calendar are documented on the public calculation methodology site, with versioned LaTeX source maintained in this repository.
The original per-city bots documented in the architecture below predate the global bot and remain available in maintenance mode.
| City | Bot |
|---|---|
| Kazan | @kazan_prayer_bot |
| Innopolis | @innopolis_prayer_bot |
The project is a set of stateless GCP Cloud Functions written in Go, backed by
Supabase Postgres for state and GCS for prayer-schedule CSV files. A single
codebase serves many bots (one per city); everything is keyed by bot_id and the
per-bot config (token, owner, timezone) is loaded from the base64-encoded APP_CONFIG
environment variable.
flowchart LR
subgraph clients [External triggers]
TG[Telegram users]
SCH[Cloud Scheduler]
UP[Prayer CSV upload]
end
subgraph functions [GCP Cloud Functions]
D["dispatcher — HTTP webhook"]
R["reminder — HTTP / cron"]
L["loader — GCS CloudEvent"]
end
PG[(Supabase Postgres)]
GCS[(GCS data bucket)]
TG -->|webhook update| D
D -->|replies| TG
SCH -->|periodic POST| R
R -->|reminders| TG
UP --> GCS
GCS -->|object finalized| L
D --> PG
R --> PG
L --> PG
Each function is an independent Go module under serverless/ that depends on a
shared root module for cross-cutting code.
| Component | Trigger | Responsibility | Code |
|---|---|---|---|
| dispatcher | Telegram webhook (HTTP POST) |
Authenticates the request by secret header, resolves/creates the chat, and routes commands & inline callbacks | serverless/dispatcher |
| reminder | Cloud Scheduler (HTTP POST) |
For every bot, evaluates each subscriber against the reminder rules and sends due notifications | serverless/reminder |
| loader | GCS object-finalized (CloudEvent) | Parses an uploaded <bot_id>.csv schedule and upserts it into Postgres |
serverless/loader |
| domain | — | Shared models & value types (Chat, PrayerDay, Reminder, Duration, errors) |
domain |
| config | — | Decodes APP_CONFIG into a per-bot config map |
config |
| internal/db | — | pgx-based Postgres repository shared by all functions |
internal/db |
| log | — | Thin structured-logging wrapper over log/slog |
log |
Infrastructure (functions, scheduler, buckets, IAM) is defined as Terraform in
infra/gcp/. Each directory has its own README.md with details.
.
├── domain/ # shared models & value types (root module)
├── config/ # APP_CONFIG loader
├── log/ # slog wrapper
├── internal/db/ # Postgres repository (pgx)
├── serverless/
│ ├── dispatcher/ # Telegram webhook handler (own go.mod)
│ ├── reminder/ # scheduled reminder sender (own go.mod)
│ └── loader/ # CSV schedule loader (own go.mod)
├── migrations/ # Goose SQL migrations
├── infra/gcp/ # Terraform (Cloud Functions, Scheduler, GCS)
└── _scripts/ # local helper scripts
The reminder function is the heart of the system. On each tick it fans out over bots and,
within a bot, over subscribers (bounded to maxConcurrentReminderSends), evaluating three
independent reminder types. State is stored per chat in the reminder JSONB column so a
reminder is sent at most once, and stale reminders are skipped after downtime.
flowchart TD
A[Scheduler POST] --> B[for each bot]
B --> C[GetSubscribers]
C --> D[GetChatsByIDs + GetPrayerDay today/next]
D --> E["fan out per chat (≤ maxConcurrentReminderSends)"]
E --> F{evaluate reminder types}
F -->|Tomorrow| G[send next-day schedule]
F -->|Soon| H["send upcoming prayer (or jamaat poll in groups)"]
F -->|Arrive| I[send prayer-arrived notice]
G --> J[UpdateReminder: message id + last_at]
H --> J
I --> J
Two tables, both multi-tenant via a composite primary key that starts with bot_id.
The flexible reminder configuration is stored as JSONB on chats.
erDiagram
chats {
bigint bot_id PK
bigint chat_id PK
text language_code
text state
jsonb reminder
boolean subscribed
timestamptz subscribed_at
timestamptz created_at
}
prayers {
bigint bot_id PK
date prayer_date PK
timestamptz fajr
timestamptz shuruq
timestamptz dhuhr
timestamptz asr
timestamptz maghrib
timestamptz isha
}
chats and prayers are linked logically by bot_id (and by date at read time); there is
no foreign key, since the two tables are populated by different functions.
GitHub secrets (per environment):
| Secret | Purpose |
|---|---|
APP_CONFIG |
Bot config JSON |
GCP_PROJECT_ID |
GCP project ID |
GCP_SA_KEY |
GCP deploy service account JSON |
GCP_TFSTATE_BUCKET |
GCS bucket for Terraform state |
SUPABASE_DB_URL |
Supabase transaction pooler URL (port 6543) — runtime DATABASE_URL on functions |
SUPABASE_DB_DIRECT_URL |
Supabase direct Postgres URL (port 5432) — Goose schema migrations |
Automatic deploys
| Trigger | Environment | What runs |
|---|---|---|
Pull request → main |
dev |
lint → validate → plan → Goose migrate → Terraform apply → webhooks → profiles |
Push / merge to main |
prod |
same full chain |
Manual deploy (hotfixes): Actions → Deploy to GCP → Run workflow → pick dev or prod and optionally a branch.
Bot configuration is managed through environment variables.
Below is an example of an APP_CONFIG value containing all bot information:
{
"648252": {
"bot_id": 648252, // Bot ID
"owner_id": 1385434843, // Bot owner ID
"location": "Europe/Moscow", // Timezone of the city
"token": "oa7GmLW3fncbOE0MTfV0mKxH/F37cShhxgZ1mjl614w", // Telegram token
"secret": "Noe&uPcwjaAxjqJU_JP4C^g2V7ZDQX" // Secret key to verify requests
},
...
}- To find your owner ID, use ID bot
- Bot ID is the first number before
:in the bot token- TOKEN:
123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 - Bot ID:
123456789
- TOKEN:
| Command | Description |
|---|---|
| today | Get today's prayer times |
| date | Get prayer times for a specific date |
| next | Find out the next prayer time |
| subscribe | Subscribe to daily reminders |
| unsubscribe | Unsubscribe from daily reminders |
| remind | Set reminder offset for the next prayer |
| language | Change the bot language |
| help | Show help message |
| bug | Report a problem to bot owner |
| feedback | Send feedback to bot owner |
| Command | Description |
|---|---|
| admin | Show admin help message |
| stats | View bot usage statistics |
| announce | Send message to all users |
| reply | Reply to user's bug/feedback message |
You do:
- Get prayer times for a city in CSV format
- Make a pull request (or open an issue) with the new file
I do:
- Create a new Telegram bot
- Upload the city file to the GCS data bucket
You do:
- Create translation text for the following files:
- ./serverless/reminder/internal/handler/languages/text.yaml
- ./serverless/dispatcher/internal/handler/languages/en.yaml (replace
enwith the new language code)
I do:
- Deploy a new version of the code
Found a bug? Want to add a new feature? Just open an issue or submit a pull request.
- Support date format for
/prayersdatecommand with leading zeros and delimiters (. / -) - Implement subscriptions & notifications
- Update text messages to be more user-friendly
- Store prayer times in memory to reduce database requests
- Add response endpoint for admin to address feedback & bug messages
- Add Jumu'ah prayer reminders on Fridays
- Add time keyboard to
/datecommand - Remove selection message for
/date&/langafter user interaction or timeout - Terminate other active channels when user sends new commands
- Add feature to delete old prayer time message when a new one is sent
- Enable admins to broadcast messages to all subscribers
- Add feature to get subscriber count for admins
- Write more robust tests for core features
- Add multi-language support (AR, RU, TT, TR, UZ)
- Implement script messages in the bot
- Set user script before command if not set
- Use script commands in notifications
- Fix prayer timetables for other languages
- Refactor code for better readability and maintainability
- Enhance logging to be more informative
- Enable using multiple bots with the same codebase
- Migrate to serverless architecture
- Automate deployment using Terraform
- Add support for multiple cities
- Add Spanish & French language support
- Add
/statscommand for bot usage statistics
- Add jamaat gathering feature for group chats
- Add support for all major world cities
cd infra/gcp
terraform plan -out plan.out
terraform show -json plan.out > plan.json
docker run --rm -it -p 9000:9000 -v "$(pwd)/plan.json:/src/plan.json" im2nguyen/rover:latest -planJSONPath=plan.json