Skip to content

sumedhpb/birthday-bot

Repository files navigation

Birthday Automation Bot 🎂

Checks Google Calendar for today's birthdays, generates personalized wishes via a local Ollama model, and posts them to Slack — runs daily via cron inside a Docker container, with automatic retries, catch-up on missed runs, and failure alerting. On days with no birthdays, a brief "no birthdays today" message is sent as a daily heartbeat.

Quick Start

1. Prerequisites

  • Docker & Docker Compose
  • Google Cloud project with:
    • OAuth2 client credentials (secret.json) — type "Desktop app"
    • Calendar API enabled
    • OAuth consent screen published (not "Testing" — test tokens expire after 7 days, breaking headless operation)
  • A running Ollama instance on your local network
  • A Slack incoming webhook URL

2. Configure Environment

cp .env.example .env

Edit .env with your actual values:

SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
OLLAMA_HOST=http://192.168.x.x:11434
OLLAMA_MODEL=llama3.2

3. Setup Credentials

mkdir -p credentials
cp /path/to/your/secret.json credentials/secret.json

4. Build

docker compose build

5. Authenticate with Google (one-time)

docker compose run --rm birthday-bot python main.py --auth

This will:

  1. Print an authorization URL to the terminal
  2. Open that URL in any browser (your laptop, phone, etc.)
  3. Sign in and grant calendar read-only access
  4. Google shows an authorization code — copy it
  5. Paste the code back into the terminal

The token is saved to credentials/token.json and reused automatically. You only need to do this once.

6. Test It Now

Full test (fetches birthdays → generates wishes → posts to Slack):

docker compose run --rm birthday-bot python main.py --force

Dry run (fetches birthdays → generates wishes → skips Slack):

docker compose run --rm birthday-bot python main.py --dry-run

7. Deploy

docker compose up -d

To check logs:

docker compose logs -f birthday-bot

Architecture

Google Calendar API  →  calendar_service.py  →  main.py
                                                   ↓
Ollama (LAN)         ←  wish_generator.py    ←────┘
                                                   ↓
Slack Webhook        ←  slack_notifier.py    ←────┘

Reliability

The bot is designed to never silently miss a day:

Feature How it works
Startup catch-up On container start, checks if today's run completed; if not, runs immediately
Dual-cron schedule Cron fires every 4 hours (0 */4 * * *) as a safety net for missed runs
Duplicate protection A .last_success_YYYY-MM-DD marker file prevents re-sending wishes
Retry with backoff Failed runs retry 3× with exponential backoff (30s → 60s → 120s)
Failure alerting Sends a Slack error notification if all retries are exhausted

Note: The bot catches up on today's missed run only — it does not backfill past days. If the container was down for multiple days, only the current day's birthdays will be processed when it comes back up.

Manual run vs. cron interaction

If you trigger a manual run while the container is also running cron:

  • --force — runs the pipeline regardless of the marker, then writes a new one. Later cron runs that day will see the marker and skip — no duplicate wishes.
  • --dry-run — runs the pipeline but does not write the marker (and doesn't post to Slack). Cron will still fire as normal.

CLI Flags

Flag Description
--auth Run OAuth2 consent flow only, then exit
--dry-run Bypasses duplicate protection. Fetches birthdays + generates wishes but skips Slack and does not write the success marker. So Cron will still run as normal
--force Bypasses duplicate protection. Runs the full pipeline and posts to Slack, then writes a new success marker. So cron will skip that day's run

Logs Directory

The logs/ directory (bind-mounted from the host) contains two types of files:

File Purpose
birthday.log Append-only log of all bot activity (successes, failures, retries). Grows over time across all days.
.last_success_YYYY-MM-DD Empty marker file (0 bytes). Created only after a successful run. Its existence is what prevents duplicate runs for that day.

The marker and log serve different roles — the log records what happened, the marker records whether today succeeded. The log file exists even when runs fail, so it cannot be used for duplicate protection.

Security

  • secret.json and token.json are bind-mounted, never baked into the Docker image
  • Slack webhook URL is passed via environment variable, never hardcoded
  • .dockerignore excludes credentials, .env, and logs from the image
  • .gitignore keeps secrets out of version control
  • Google enforces calendar.readonly scope server-side — even a leaked token can only read calendar data

Revoking Access

If token.json is compromised:

  1. Go to Google Account → Third-party apps
  2. Find your app → Remove Access
  3. Delete credentials/token.json
  4. Re-run docker compose run --rm birthday-bot python main.py --auth

About

Checks Google Calendar for today's birthdays, generates personalized wishes via a local Ollama model, and posts them to Slack — runs daily via cron inside a Docker container, with automatic retries, catch-up on missed runs, and failure alerting.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors