Skip to content

Automated follow-ups

Email follow-up is a memory tax. mxr’s automated follow-ups remove it:

  • Send a message and configure a reminder — if no reply lands by the given time, mxr surfaces it back to you.
  • Schedule a draft for a future send time — write now, deliver later.

Both run as 60-second daemon background loops. They survive daemon restarts, idempotently.

Set on outbound messages. Stored in the auto_reminders table; fired by the auto_reminders_loop.

Terminal window
# After sending, add a follow-up reminder:
mxr remind MESSAGE_ID --when "in 5d"
mxr remind MESSAGE_ID --when "monday 9am"
# Cancel before it fires:
mxr remind MESSAGE_ID --cancel

When the time elapses, mxr emits a ReminderTriggered event so any connected client (TUI, HTTP-bridge consumer, agent) can surface the follow-up. Re-setting the reminder on the same message replaces the existing schedule.

Schedule a draft for a future send time. The daemon’s flusher loop picks up due drafts and runs them through the same send_stored_draft pipeline that interactive sends use — so the message arrives at the provider exactly as it would have if you’d sent it manually then.

Terminal window
# Compose a draft (any usual flow), then schedule it:
mxr send DRAFT_ID --at "in 1h"
mxr send DRAFT_ID --at "tomorrow 9am"
mxr send DRAFT_ID --at "monday 17:00"
# Cancel a scheduled send:
mxr unsend DRAFT_ID

The draft itself is preserved on cancel — you can edit and reschedule.

The flusher clears the send_at flag on a draft before invoking the send. If the daemon crashes mid-send, the draft’s status state machine takes over: a draft in 'sending' whose heartbeat is older than 1 hour is considered orphaned and reset to 'draft' on the next daemon startup, ready for the user to retry.

Both mxr remind --when and mxr send --at accept the same forms as mxr snooze --until:

  • Relative durations: in 30m, in 2h, in 5d, in 2w
  • Named days: tomorrow, monday, tuesday, …, sunday (also three-letter abbreviations: mon, tue, …)
  • Day + time: tomorrow 9am, monday 17:00, friday 5pm
  • Today: today 17:00 (must be a future time)
  • RFC3339: 2026-06-01T15:00:00Z

Past times are rejected. “Today” without a specific time is rejected (too ambiguous).

  • The reminder + send-later loops run every 60 seconds. The minimum practical scheduling resolution is therefore 60s; finer-grained scheduling would require ticker tuning.
  • Both loops are crash-safe by virtue of the underlying state being in SQLite — restarting the daemon picks up where it left off.
  • Cancelled reminders / sends are non-destructive: the row stays in the table with cancelled_at set, so analytics can answer “how often did you actually need this nudge?” later.

If a reminder fires on an outbound message that’s still awaiting a reply, the natural next step is to flag the original outbound (or the thread it belongs to) for reply-later — so it shows up in mxr replies alongside the rest of your follow-up queue:

Terminal window
mxr replies add OUTBOUND_MESSAGE_ID

Future polish: this could happen automatically when the reminder fires. Today it’s a manual one-liner.