Defer weekend document reminders to Monday
Reminders that fall due on Saturday or Sunday no longer send over the weekend or spend the escalation ladder unused. SQL keeps calendar cadence and rolls weekend due times to Monday at the same clock hour. The first debounced notice is unchanged. Co-authored-by: Sacha Al Himdani <sacha@probo.com> Signed-off-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
@@ -420,6 +420,7 @@ func (d *DocumentVersionApprovalDecisions) LoadNextDueGroupForNotification(
|
|||||||
debounceBefore time.Time,
|
debounceBefore time.Time,
|
||||||
reminderInterval time.Duration,
|
reminderInterval time.Duration,
|
||||||
) error {
|
) error {
|
||||||
|
// Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour.
|
||||||
q := `
|
q := `
|
||||||
WITH next_group AS (
|
WITH next_group AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -432,7 +433,25 @@ WITH next_group AS (
|
|||||||
AND notification_count < @max_notifications
|
AND notification_count < @max_notifications
|
||||||
AND (
|
AND (
|
||||||
(notification_count = 0 AND created_at < @debounce_before)
|
(notification_count = 0 AND created_at < @debounce_before)
|
||||||
OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count))
|
OR (
|
||||||
|
notification_count > 0
|
||||||
|
AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5
|
||||||
|
AND @now::timestamptz > (
|
||||||
|
SELECT
|
||||||
|
(due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc)
|
||||||
|
WHEN 6 THEN interval '2 days'
|
||||||
|
WHEN 7 THEN interval '1 day'
|
||||||
|
ELSE interval '0'
|
||||||
|
END) AT TIME ZONE 'UTC'
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
last_notified_at
|
||||||
|
+ make_interval(secs => @reminder_interval_seconds * notification_count)
|
||||||
|
) AT TIME ZONE 'UTC' AS due_at_utc
|
||||||
|
) AS reminder
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -531,6 +550,7 @@ func (d DocumentVersionApprovalDecisions) ClaimForNotification(
|
|||||||
ids[i] = decision.ID
|
ids[i] = decision.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour.
|
||||||
q := `
|
q := `
|
||||||
UPDATE document_version_approval_decisions
|
UPDATE document_version_approval_decisions
|
||||||
SET
|
SET
|
||||||
@@ -542,7 +562,25 @@ WHERE
|
|||||||
AND notification_count < @max_notifications
|
AND notification_count < @max_notifications
|
||||||
AND (
|
AND (
|
||||||
(notification_count = 0 AND created_at < @debounce_before)
|
(notification_count = 0 AND created_at < @debounce_before)
|
||||||
OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count))
|
OR (
|
||||||
|
notification_count > 0
|
||||||
|
AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5
|
||||||
|
AND @now::timestamptz > (
|
||||||
|
SELECT
|
||||||
|
(due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc)
|
||||||
|
WHEN 6 THEN interval '2 days'
|
||||||
|
WHEN 7 THEN interval '1 day'
|
||||||
|
ELSE interval '0'
|
||||||
|
END) AT TIME ZONE 'UTC'
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
last_notified_at
|
||||||
|
+ make_interval(secs => @reminder_interval_seconds * notification_count)
|
||||||
|
) AT TIME ZONE 'UTC' AS due_at_utc
|
||||||
|
) AS reminder
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -412,7 +412,9 @@ WHERE
|
|||||||
// request gets: the first notice plus three reminders. A request is due for its
|
// request gets: the first notice plus three reminders. A request is due for its
|
||||||
// next email once it is past its scheduled offset — the first email after the
|
// next email once it is past its scheduled offset — the first email after the
|
||||||
// debounce delay, then reminders at 1x, 2x and 3x the reminder interval after
|
// debounce delay, then reminders at 1x, 2x and 3x the reminder interval after
|
||||||
// the previous email — and stops once it reaches this cap.
|
// the previous email — and stops once it reaches this cap. Reminder sends that
|
||||||
|
// fall on Saturday or Sunday are deferred to Monday at the same clock time;
|
||||||
|
// the first debounced notice is unchanged.
|
||||||
const documentNotificationMaxCount = 4
|
const documentNotificationMaxCount = 4
|
||||||
|
|
||||||
func remainingNotificationIDs(all []gid.GID, claimed []gid.GID) []gid.GID {
|
func remainingNotificationIDs(all []gid.GID, claimed []gid.GID) []gid.GID {
|
||||||
@@ -445,6 +447,7 @@ func (pvss *DocumentVersionSignatures) LoadNextDueGroupForNotification(
|
|||||||
debounceBefore time.Time,
|
debounceBefore time.Time,
|
||||||
reminderInterval time.Duration,
|
reminderInterval time.Duration,
|
||||||
) error {
|
) error {
|
||||||
|
// Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour.
|
||||||
q := `
|
q := `
|
||||||
WITH next_group AS (
|
WITH next_group AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -457,7 +460,25 @@ WITH next_group AS (
|
|||||||
AND notification_count < @max_notifications
|
AND notification_count < @max_notifications
|
||||||
AND (
|
AND (
|
||||||
(notification_count = 0 AND requested_at < @debounce_before)
|
(notification_count = 0 AND requested_at < @debounce_before)
|
||||||
OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count))
|
OR (
|
||||||
|
notification_count > 0
|
||||||
|
AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5
|
||||||
|
AND @now::timestamptz > (
|
||||||
|
SELECT
|
||||||
|
(due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc)
|
||||||
|
WHEN 6 THEN interval '2 days'
|
||||||
|
WHEN 7 THEN interval '1 day'
|
||||||
|
ELSE interval '0'
|
||||||
|
END) AT TIME ZONE 'UTC'
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
last_notified_at
|
||||||
|
+ make_interval(secs => @reminder_interval_seconds * notification_count)
|
||||||
|
) AT TIME ZONE 'UTC' AS due_at_utc
|
||||||
|
) AS reminder
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -554,6 +575,7 @@ func (pvss DocumentVersionSignatures) ClaimForNotification(
|
|||||||
ids[i] = signature.ID
|
ids[i] = signature.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour.
|
||||||
q := `
|
q := `
|
||||||
UPDATE document_version_signatures
|
UPDATE document_version_signatures
|
||||||
SET
|
SET
|
||||||
@@ -565,7 +587,25 @@ WHERE
|
|||||||
AND notification_count < @max_notifications
|
AND notification_count < @max_notifications
|
||||||
AND (
|
AND (
|
||||||
(notification_count = 0 AND requested_at < @debounce_before)
|
(notification_count = 0 AND requested_at < @debounce_before)
|
||||||
OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count))
|
OR (
|
||||||
|
notification_count > 0
|
||||||
|
AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5
|
||||||
|
AND @now::timestamptz > (
|
||||||
|
SELECT
|
||||||
|
(due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc)
|
||||||
|
WHEN 6 THEN interval '2 days'
|
||||||
|
WHEN 7 THEN interval '1 day'
|
||||||
|
ELSE interval '0'
|
||||||
|
END) AT TIME ZONE 'UTC'
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
last_notified_at
|
||||||
|
+ make_interval(secs => @reminder_interval_seconds * notification_count)
|
||||||
|
) AT TIME ZONE 'UTC' AS due_at_utc
|
||||||
|
) AS reminder
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func New() *Implm {
|
|||||||
Document: DocumentNotificationConfig{
|
Document: DocumentNotificationConfig{
|
||||||
Interval: 300, // 5 minutes
|
Interval: 300, // 5 minutes
|
||||||
DebounceDelay: 900, // 15 minutes
|
DebounceDelay: 900, // 15 minutes
|
||||||
ReminderInterval: 86400, // 1 day base cadence (1x, 2x, 3x)
|
ReminderInterval: 86400, // 1 day base cadence (1x, 2x, 3x; weekend reminders → Monday)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CustomDomains: CustomDomainsConfig{
|
CustomDomains: CustomDomainsConfig{
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ type DocumentNotificationConfig struct {
|
|||||||
// DebounceDelay is how long a request must have been pending before its
|
// DebounceDelay is how long a request must have been pending before its
|
||||||
// first notification is sent.
|
// first notification is sent.
|
||||||
DebounceDelay int `json:"debounce-delay"`
|
DebounceDelay int `json:"debounce-delay"`
|
||||||
// ReminderInterval is the base reminder cadence. Reminders are sent at
|
// ReminderInterval is the base reminder cadence in seconds. Reminders are
|
||||||
// 1x, 2x and 3x this interval after the previous email, then stop.
|
// sent at 1x, 2x and 3x this interval after the previous email, then stop.
|
||||||
|
// Reminder sends that fall on Saturday or Sunday are deferred to Monday at
|
||||||
|
// the same clock time; the first (debounced) notice is unchanged.
|
||||||
ReminderInterval int `json:"reminder-interval"`
|
ReminderInterval int `json:"reminder-interval"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user