The reminder engine
The reminder engine is a timer inside the bot. Channel reminders run on a tick, one pass of the timer that repeats every few minutes. Follow-ups have a timer of their own, see Follow-ups. Each tick it asks one question per member and rule: is a post overdue, and if so, has the member already posted? A rule is one channel reminder an admin set up, see Reminder rules.
Four settings shape it, described in Configuration: REMINDERS_ENABLED (when false the engine never starts and the bot logs [reminders] disabled), REMINDER_TICK_MINUTES (15 by default), REMINDER_LATE_LIMIT_MINUTES (60 by default; a reminder more than this late is dropped, not sent late) and REMINDER_ALLOWLIST. When the allowlist (a list of member ids) is set, only those members get reminder, follow-up and setup DMs, and it limits every other DM the bot starts itself, such as summaries and ticket prompts. A follow-up can still be saved by someone outside the list; it is skipped when due.
The tick
Section titled “The tick”The engine starts after commands are registered, runs a tick at once, then one every tick interval. A tick still running when the next is due makes that one skip.
Each tick does four things in order:
- Fill in the default channel for the
standuprule, see below. - Channel watch. Check every channel rule against every eligible member.
- Follow-ups. Send any follow-up that has come due, in case the follow-up timer missed one.
- Setup DMs. DM members who have not finished setup. These are counted as
nudgesin the log.
A tick that sends anything, or has a reminder or follow-up DM fail, logs one line: [reminders] tick: sent=1 failed=0 followups=0 nudges=0. A failed setup DM is logged on its own line. A tick with nothing to do logs nothing.
Who is checked
Section titled “Who is checked”A member is checked for a rule when all of these hold:
- They have not left the server.
- They have a timezone and both working hours set.
- The allowlist is empty, or lists them.
- They have not switched that rule off for themselves.
The rule must be enabled and have a channel and an anchor (start or end of day).
When a reminder is due
Section titled “When a reminder is due”For each recent shift day (the member’s work day, named by the local date it starts on), the engine works out:
- Anchor. The member’s start time for a start-of-day rule, their end time for an end-of-day rule, or their custom time if set (the time the member chose with
/reminders time). - Due time. Anchor plus the rule’s grace minutes (0 to 1440), its wait after the anchor. A custom time replaces both: the due time is the custom time.
- Send window. From the due time until the due time plus the late limit. Both ends are included.
Saturday and Sunday shift days, in the member’s own timezone, never produce reminders. There is no holiday support.
Worked timeline
Section titled “Worked timeline”A member in New York works 09:00 to 17:00. The standup rule is start of day with 120 minutes grace. The tick is 15 minutes and the late limit 60.
| Local time | What happens |
|---|---|
| 09:00 | Anchor. Nothing yet. |
| 10:53 | A tick runs. Not due. |
| 11:00 | Due time (09:00 plus 120 minutes). The send window opens. |
| 11:08 | A tick runs. The member has not posted since 00:00 (the start of the posting window), so the bot claims the run (records it first, see Sending once), sends the DM and records it. |
| 11:23 | A tick runs. The run is already recorded and is skipped. |
| 12:00 | The window closes. Had the bot been down since 11:00, nothing would be sent after this. |
Overnight shifts
Section titled “Overnight shifts”An end-of-day time at or before the start time falls on the next calendar day, so a member working 16:00 to 00:00 with 0 grace is due at midnight after the shift. The weekend check uses the shift day: Friday’s shift is reminded early Saturday, Saturday’s shift not at all. The posting window for such a shift does not start at midnight, see What counts as posted.
What counts as posted
Section titled “What counts as posted”The engine checks from the start of the posting window up to now. The window usually starts at midnight at the start of the shift day, in the member’s timezone. If the due time lands on a later calendar day than the shift day, as with overnight shifts or a long grace, it starts at the work start minus a lead, or at the anchor time if that is earlier. The lead is up to 3 hours and never more than half the gap between shifts, so it cannot reach into the previous shift.
For 16:00 to 00:00 with an end-of-day rule and 0 grace, the due time is midnight on the next day. The gap between shifts is 16 hours, so the lead is 3 hours and the window starts at 13:00 on the shift day. A post made before 13:00 does not count. For the New York standup above, the due time is the same day, so the window starts at 00:00.
A member has posted if the bot has stored a normal message or reply from them, not deleted, in the watched channel or one of its threads. If the rule needs an attachment, the message must have one.
If the stored copy shows nothing, the engine asks Discord directly before sending, because a very recent message may not be stored yet. It reads up to 3 pages of 100 messages after the window start (so on a busy channel only the first 300), in the channel itself and not its threads. Only normal messages and replies count. One read is kept for 60 seconds and reused for every member with the same channel, window start and attachment requirement. If the live check fails, nothing is sent and the next tick tries again.
The DM
Section titled “The DM”The bot fills in the rule’s placeholders ({greeting}, {name}, {channel}, {previous}, and {link} and {note} for follow-ups), listed with their values in Reminder rules. {previous} is the member’s latest post from before the posting window started, and is empty if there is none or the lookup fails.
The built-in standup message, for a member called Sam at 09:30 local time, with no earlier post:
Doge Bot sends you a DM
Good morning Sam, looks like you haven’t posted in <#123456789012345678> yet. This is a friendly reminder to do so whenever you get the chance.
A reminder never contains the text of anyone’s message, only a link.
Default channel for standup
Section titled “Default channel for standup”Migrations create the rules without a channel. Each tick, if the standup rule has none, the engine sets it to a text or announcement channel named stand-up, ignoring case; a channel named standup is not found. If several match, the one the bot stored first wins. It never overwrites a channel that is set, and until the channel exists standup does nothing. art-eod ships off with no channel. See Reminder rules.
The check after /hours set
Section titled “The check after /hours set”/hours set asks the engine to check that one member now instead of waiting for the next tick. It uses the same rules as a tick, with these differences:
- Only enabled channel rules that anchor to the start of the day and have a channel are looked at. End-of-day rules are never checked this way.
- The grace minutes are ignored, so the due time is the work start itself.
- The late limit is ignored. The send window runs to the end of the shift, so the check can send at any point while the shift is running.
- A custom time that is later than now is not due yet, so nothing is sent for that rule.
- It does nothing when reminders are off, the member is outside the allowlist, the member turned the rule off, or the rule is disabled.
- It fills in the default
standupchannel first, as a tick does.
Everything else is unchanged: weekend shift days send nothing, a member who has posted gets nothing, and the database is checked before Discord. A DM that fails is recorded as failed and the channel is not reported back.
The same claim protects it, so it counts as that shift day’s reminder: a second /hours set the same day, and the engine’s later tick, send nothing. The reply to /hours set lists the channels actually sent, joined by commas. If the check fails, the hours are still saved, the reply is unchanged and the failure is logged as [reminders] on-demand check failed. /hours clear never triggers it.
Sending once
Section titled “Sending once”The engine claims a run before sending, by inserting a record of it. A unique database constraint makes that insert fail if the same rule, member and due time already exist. So:
- At most one reminder per rule, member and due time, even with two bot processes running.
- At most one per rule, member and shift day. If a member changes their hours after being reminded, they are not reminded again for that day.
- A crash between claim and send loses that reminder. Channel reminders are never reclaimed. An error that does not crash the bot is logged as
[reminders] reminder <rule name> failed after claim: ...and the run is recorded as failed, so it never stays claimed. - A failed DM is not retried. It is recorded as failed with a reason.
| Failure reason | Meaning |
|---|---|
dm_closed | The member has server DMs off or blocked the bot. |
discord_<code> | Another Discord error, for example discord_10013. |
| Error text, up to 300 characters | A non-Discord error while sending. |
internal_error | Sending, or anything after the run was claimed, threw an unexpected error. |
member_not_found, rule_not_found | Follow-ups only. The member or rule record no longer exists. |
not_allowlisted, left_guild | Follow-ups only, with the status skipped instead of failed. The member is outside the allowlist, or has left the server. |
Follow-ups
Section titled “Follow-ups”A follow-up is a saved record with a due time, a message link and an optional note. Saving one is covered in Follow-ups, and how a tag is read in Asking questions. Each is sent by its own timer, not the tick, so it arrives at the second it was due.
The engine keeps one timer, set for the earliest scheduled follow-up. When it fires, the engine claims every due follow-up, up to 50 at a time for up to 30 seconds, sends them, then sets the timer for the next one. The timer is also set at start, after every tick and whenever a follow-up is saved (from /follow, the picker, the form or a tag). It never sleeps longer than the tick interval, so a follow-up due far ahead is picked up by a later tick. If setting or running the timer fails, the failure is logged and the next tick sets it again. The timer does not run when reminders are switched off.
- No late limit: an overdue follow-up is still sent.
- Weekends, working hours and timezone do not matter.
- A member who has left the server cannot save one.
- If the bot stops mid-batch, unsent claims are released for the next start. A claim left over for more than 10 minutes, for example after a crash, goes back to scheduled, which can very rarely send the same DM twice.
- A follow-up for a member outside the allowlist, or who left, is marked skipped with reason
not_allowlistedorleft_guild. - No command can disable the follow-up rule. If it is disabled directly in the database, new follow-ups are refused but scheduled ones still send.
The built-in message is {greeting} {name}, this is a friendly reminder to follow up on {link}. {note}. An admin can change the wording. It never contains the message text.
Setup DMs
Section titled “Setup DMs”A member missing a timezone, a start time or an end time gets one DM, ever. Up to 25 members are nudged per tick. The member is marked nudged before the DM goes out, so a failed DM is not retried.
Doge Bot sends you a DM
Hi Sam, set your timezone and working hours so reminders reach you at the right time. Open the server and run /hours set in any channel with your start time, end time and timezone.
The greeting is Hi, when the member’s name is empty.
- Rolling out the bot for turning this on safely.
- Channel reminders for the member’s view.