---
title: 'Discord Thread Scheduler'
url: 'https://wiki.teletypegames.org/services/statusbot'
markdown: 'https://wiki.teletypegames.org/services/statusbot.md'
lang: en
date: '2026-03-06'
description: 'A Free Pascal bot that creates a Discord thread once a week at a fixed time and posts the first message into it. Good for weekly status updates and check-ins. Configuration Environment variables; env-example is the template. Variable Description Example RUN_AT The schedule in DOW:HH:MM for…'
taxonomy:
  tag:
    - howto
---

# Discord Thread Scheduler

> A Free Pascal bot that creates a Discord thread once a week at a fixed time and posts the first message into it. Good for weekly status updates and check-ins.

# Configuration

Environment variables; `env-example` is the template.

| Variable | Description | Example |
|---|---|---|
| `RUN_AT` | The schedule in `DOW:HH:MM` format (1=Sun, 7=Sat). | `3:09:00` (Tuesday at 09:00) |
| `DISCORD_BOT_TOKEN` | Your Discord Bot's authentication token. | `MTIzNDU2...` |
| `DISCORD_CHANNEL_ID` | The ID of the channel where the thread will be created. | `123456789012345678` |
| `THREAD_NAME` | The base name for the thread. The current date is appended automatically. | `Weekly Status` |
| `THREAD_MESSAGE` | The first message to post in the thread. Supports Discord mentions. | `@here Please post updates!` |
| `ARCHIVE_DURATION` | (Optional) Minutes until the thread is archived. Defaults to 10080 (7 days). | `1440` (1 day) |

`RUN_AT` is `DayOfWeek:Hour:Minute` — day 1 (Sunday) to 7 (Saturday), hour 0–23, minute 0–59. `RUN_AT=2:14:30` means every Monday at 14:30.

# How it works

1. On startup it parses `RUN_AT` and validates the environment.
2. It checks the system time every 10 seconds.
3. On a match it calls the Discord API: create the thread named `THREAD_NAME (YYYY-MM-DD)`, take the new thread id from the response, then post `THREAD_MESSAGE` into it.
4. It waits until the next minute, so the same scheduled minute cannot fire twice.

# Requirements

A Discord bot with `Send Messages` and `Create Public Threads` in the target channel. Runs as a standalone binary or in Docker.

# Deploy

The bot runs in `/srv/stacks/statusbot` on the server. A push to master that changes `statusbot.pas`, the `Dockerfile` or `compose.yaml` deploys it from Woodpecker (`ci-deploy statusbot`, see [Deployment](https://wiki.teletypegames.org/infrastructure/deployment)); the pipeline first compiles it with `fpc`. The `.env` stays on the server.

# Repository

https://git.teletypegames.org/services/statusbot

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Services](https://wiki.teletypegames.org/services.md)
- Previous: [Scriptorium BBS](https://wiki.teletypegames.org/services/scriptorium-bbs.md)
- Next: [Teletype BBS](https://wiki.teletypegames.org/services/teletype-bbs.md)
