From 42110c36f1fcc449f807bf6acc6d72f8831a22bb Mon Sep 17 00:00:00 2001 From: Doug Koerich Date: Thu, 21 May 2026 14:41:46 -0300 Subject: [PATCH 1/4] TASK-144646: Documentation for new curfew module (SparkPost/Momentum#1228) Signed-off-by: Doug Koerich --- content/momentum/4/4-console-commands.md | 5 + content/momentum/4/config-options-summary.md | 4 + content/momentum/4/modules/curfew.md | 272 ++++++++++++++++++ content/momentum/4/modules/index.md | 1 + .../momentum/4/modules/summary-all-modules.md | 1 + content/momentum/changelog/5/5-3-0.md | 1 + 6 files changed, 284 insertions(+) create mode 100644 content/momentum/4/modules/curfew.md diff --git a/content/momentum/4/4-console-commands.md b/content/momentum/4/4-console-commands.md index 4136666f6..c13d4580c 100644 --- a/content/momentum/4/4-console-commands.md +++ b/content/momentum/4/4-console-commands.md @@ -75,6 +75,11 @@ This table lists all console commands alphabetically giving a brief description. | [counter list](/momentum/4/console-commands/counter) – List all counters counter | 4.0 |   | policy | | [counter reset](/momentum/4/console-commands/counter) – Reset a counter to zero | 4.0 |   | policy | | [counter unlink](/momentum/4/console-commands/counter) – Unlink a counter | 4.0 |   | policy | +| [curfew fuzz](/momentum/4/modules/curfew#modules.curfew.console) – Report the lift-fuzz value that would currently apply to a domain | 5.3 | curfew | module | +| [curfew journal](/momentum/4/modules/curfew#modules.curfew.console) – Force an immediate journal sweep of curfew rule transitions | 5.3 | curfew | module | +| [curfew reload](/momentum/4/modules/curfew#modules.curfew.console) – Re-read the curfew schedule file | 5.3 | curfew | module | +| [curfew show](/momentum/4/modules/curfew#modules.curfew.console) – Dump every loaded curfew rule | 5.3 | curfew | module | +| [curfew status](/momentum/4/modules/curfew#modules.curfew.console) – Show a one-line curfew module summary | 5.3 | curfew | module | | [delay_dsn:instance_name show pending events](/momentum/4/modules/delay-dsn#modules.delay_dsn.console) – List all pending events | 4.0 | delay_dsn | module | | [delayed](/momentum/4/console-commands/delayed) – Show domains with delayed queue size bigger than threshold | 4.0 |   | queue admin | | [dig](/momentum/4/console-commands/dig) – Submit a domain for dns MX query | 4.0 |   | dns | diff --git a/content/momentum/4/config-options-summary.md b/content/momentum/4/config-options-summary.md index 42d6f2930..b08095086 100644 --- a/content/momentum/4/config-options-summary.md +++ b/content/momentum/4/config-options-summary.md @@ -108,6 +108,7 @@ The `Version` column indicated the version(s) of Momentum that support the optio | [critical](/momentum/4/config/ref-debug-flags) – Set the debug level | na | ALL | 4.0 and later | debug_flags | | [crypto_engine](/momentum/4/config/ref-crypto-engine) – Enable hardware cryptography acceleration | both |   | 4.0 and later | global | | [crypto_lock_method](/momentum/4/config/crypto-lock-method) – Set the locking method used by the TLS layer | receiving and sending | EC_SSL_DEFAULTLOCK (*non-dynamic*) | 4.0 and later | global | +| [curfew](/momentum/4/modules/curfew#modules.curfew.configuration) *(scope)* – Configure the curfew (scheduled delivery suspension) module | sending | | 5.3 and later | global | | [debug](/momentum/4/config/ref-debug-flags) – Set the debug level | na |   | 4.0 and later | debug_flags | | [debug_flags](/momentum/4/config/ref-debug-flags) *(scope)* – Configure debug verbosity | na |   | 4.0 and later | global | | [debug_level](/momentum/4/4-module-config) – Set the module debug level (applicable to all modules) (cluster-specific) | na | error | 4.0 and later | cluster | @@ -172,6 +173,7 @@ The `Version` column indicated the version(s) of Momentum that support the optio | [file_mode](/momentum/4/config/ref-eccluster-conf#eccluster.conf.logs.logfile) – Octal representation of the file permissions (cluster logs) | na |   | 4.0 and later | logs | | [force_fsync](/momentum/4/config/ref-force-fsync) – Ensure that data is synced to disk on reception | receiving | false | 4.0 and later | global | | [fully_resolve_before_smtp](/momentum/4/config/ref-fully-resolve-before-smtp) – Resolve all MX and A records before attempting delivery | sending | true | 4.0 and later | binding, binding_group, domain, global | +| [Fuzz_Seconds](/momentum/4/modules/curfew#modules.curfew.fuzz_seconds) – Spread the curfew "thundering herd" after a window closes | sending | 60 | 5.3 and later | curfew | | [gateway](/momentum/4/config/ref-gateway) – Configure a static SMTP route for mail | sending |   | 4.0 and later | binding, binding_group, domain, global | | [gcm_application_id](/momentum/3/3-push/push-gcm-gcm-application-id) – Define the package name of the Android application allowed to received notifications | sending |   | 4.0 and later | binding, binding_group, domain, global | | [gcm_authorization_token_id](/momentum/3/3-push/push-gcm-gcm-authorization-token-id) – Authorization token that permits sending Google push notifications | sending |   | 4.0 and later | binding, binding_group, domain, global | @@ -314,6 +316,7 @@ The `Version` column indicated the version(s) of Momentum that support the optio | [replicate](/momentum/4/config/ref-authorization) *(scope)* – Define a role within an authorization stanza | na |   | 4.0 and later | authorization | | [routes](/momentum/4/config/ref-routes) – Configure message routing | sending |   | 4.0 and later | domain, global | | [rset_timeout](/momentum/4/config/ref-rset-timeout) – Set the timeout after RSET | sending | 30 | 4.0 and later | binding, binding_group, domain, global | +| [Schedule_File](/momentum/4/modules/curfew#modules.curfew.schedule_file) – Path to the curfew schedule file | sending | | 5.3 and later | curfew | | [scheduler](/momentum/4/config/ref-scheduler) – Override the default IO scheduler | na | (*non-dynamic*) | 4.0 and later | global | | [scope_max_outbound_connections](/momentum/4/config/ref-scope-max-outbound-connections) – Provide traffic shaping for outbound connections | sending |   | 4.0 and later | binding, binding_group, domain, global | | [security](/momentum/4/config/ref-security) *(scope)* – Scope for defining which permissions are retained by which user | na | (*non-dynamic*) | 4.0 and later | global | @@ -348,6 +351,7 @@ The `Version` column indicated the version(s) of Momentum that support the optio | [threadpool](/momentum/4/config/ref-threadpool) *(scope)* – Configure thread pool specific options | na |   | 4.0 and later | global | | [timeout](/momentum/4/control-listener#control_listener.config) – Timeout for idle control connections on Control_Listeners | receiving | 60 | 4.0 and later | control_listener | | [timestampformat](/momentum/4/config/ref-timestampformat) – Set the timestamp format used when logging to stderr | na | [%a %d %b %Y %H:%M:%S] | 4.0 and later | global | +| [Timezone](/momentum/4/modules/curfew#modules.curfew.timezone) – Timezone in which curfew cron fields are interpreted | sending | local | 5.3 and later | curfew | | [tls](/momentum/4/config/ref-tls) – Determine whether to use a TLS connection for outbound mail | sending | no | 4.0 and later | binding, binding_group, domain, global | | [tls_allow_renegotiation](/momentum/4/config/tls-allow-renegotiation) – Determine whether to enable TLS renegotiation | receiving and sending | true | 4.0 and later | ecstream_listener, esmtp_listener, http_listener, listen, listen, pathway, pathway_group, peer, xmpp_listener | | [tls_ca](/momentum/4/config/tls-ca) – Specify certificate authority for outbound mail | sending |   | 4.0 and later | binding, binding_group, domain, global | diff --git a/content/momentum/4/modules/curfew.md b/content/momentum/4/modules/curfew.md new file mode 100644 index 000000000..4e79ae13e --- /dev/null +++ b/content/momentum/4/modules/curfew.md @@ -0,0 +1,272 @@ +--- +lastUpdated: "05/21/2026" +title: "curfew – Scheduled Delivery Suspension" +description: "The curfew module schedules quiet hours blackout windows during which Momentum will not attempt deliveries for selected bindings binding groups or domains The schedule is expressed in crontab style syntax and is read from a plain text file Curfew replaces ad hoc operator workflows that toggle suspend_delivery from cron jobs..." +--- + + + +The `curfew` module schedules *quiet hours* (blackout windows) during which Momentum will not attempt deliveries for selected bindings, binding groups, or domains. The schedule is expressed in crontab-style syntax and is read from a plain-text file. + +Curfew replaces *ad-hoc* operator workflows that toggle [`suspend_delivery`](/momentum/4/config/ref-suspend-delivery), e.g., from external cron jobs. Driving suspension state from outside the MTA is fragile: a missed cron firing can leave messages stuck on the spool, and a race between the cron job and a configuration commit can leave the binding suspended forever. Curfew evaluates its rules from inside the suspension decision path on every delivery attempt, so quiet hours engage and lift on schedule without operator intervention and without persistent state changes to the binding/domain scope. + +### Note + +Curfew does **not** fail or bounce messages. While a rule is matching, the affected (binding, domain) pairs return suspended verdicts; queued mail simply waits in the messages queue and is retried as soon as the window closes. + +### Configuration + +The `curfew` module is a singleton and is loaded automatically when any of its configuration options is set or its console command is invoked. The minimum configuration is a single option — the path to the schedule file: + + + + +``` +curfew { + Schedule_File = "/etc/ecelerity/curfew.schedule" +} +``` + +That is enough to enable the feature: [`Timezone`](#modules.curfew.timezone) and [`Fuzz_Seconds`](#modules.curfew.fuzz_seconds) both have built-in defaults (`local` and `60` respectively), so they only need to appear when the operator wants to override them. A fully spelled-out configuration looks like this: + + + + +``` +curfew { + Schedule_File = "/etc/ecelerity/curfew.schedule" + Timezone = "local" + Fuzz_Seconds = 60 +} +``` + +Any change to `Schedule_File`, `Timezone`, or `Fuzz_Seconds` is picked up automatically by the next `config reload` (or any other operation that triggers a configuration commit). The schedule file itself can also be reloaded on demand from the console — see [Console Commands](#modules.curfew.console) below. + +### Schedule File Format + +The schedule file is a plain-text file. Lines starting with `#` and blank lines are ignored. Every non-comment line declares one rule and contains exactly eight whitespace-separated fields: + +``` +binding domain on|off minute hour day-of-month month day-of-week +``` + +The column separator is whitespace (spaces or tabs) rather than comma, because cron-style field values themselves use the comma as a list separator (for example, `0,15,30,45`). + +
+ +
binding
+ +
+ +The name of a binding **or** a binding group to which the rule applies. A literal `*` (asterisk) matches every binding. Matching is case-insensitive. + +
+ +
domain
+ +
+ +The destination domain to which the rule applies. A literal `*` matches every domain. Matching is case-insensitive. + +
+ +
on|off
+ +
+ +Whether the rule is enforced. Only `on` rules are honored. The `off` keyword is reserved: rows declared as `off` are parsed (so the file remains valid) but otherwise ignored — they are a placeholder for future negative-rule semantics. A line whose third field is neither `on` nor `off` is a parse error and causes the previously loaded schedule to be retained. + +
+ +
minute, hour, day-of-month, month, day-of-week
+ +
+ +Standard crontab fields. Each field accepts a literal value, the wildcard `*`, an inclusive range (`1-5`), a comma-separated list (`1,3,5`), or a step (`*/15`, `0-30/2`). The legal value ranges are: + +* minute — `0`–`59` + +* hour — `0`–`23` + +* day-of-month — `1`–`31` + +* month — `1`–`12` + +* day-of-week — `0`–`6` (with `0` = Sunday) + +Following Vixie cron semantics, the day-of-month and day-of-week fields are combined with **OR**: if both fields are restricted (neither is a literal `*`), the rule matches when **either** the day-of-month field matches **or** the day-of-week field matches. When exactly one of the two is a literal `*`, only the restricted side constrains; when both are `*`, every day matches. + +
+ +
+ +A rule's time match is evaluated against the current wall-clock minute in the [`Timezone`](#modules.curfew.timezone) configured for the module. + +#### Example Schedule + +``` +# Quiet hours: weekdays, 18:00 through 23:59, for every binding +# delivering to gmail.com — operator-mandated nighttime hold. +* gmail.com on * 18-23 * * 1-5 + +# Weekend-only blackout for the marketing binding to all domains. +marketing * on * * * * 0,6 + +# Weekly maintenance window: every Monday, 02:00-02:59, +# for everything in the "production" binding group. +production * on * 2 * * 1 +``` + +### Options Valid in the `curfew` Scope + +The following configuration options are valid only inside the `curfew { ... }` scope. + +
+ +
Schedule_File
+ +
+ +The absolute path of the schedule file to load. **Default value is empty, which disables the feature**: with no schedule file configured, the module loads but never produces a suspension verdict. + +If `Schedule_File` is set but the file cannot be opened, the previously loaded schedule (if any) is retained and an error is logged. The same is true when the file contains a parse error: the new file is rejected as a whole — partial reloads never happen — and the previous schedule remains in effect. + +
+ +
Timezone
+ +
+ +The timezone in which the cron fields of every rule are interpreted. Accepted values are `local` and `UTC` (case-insensitive). Default value is `local`, which matches standard crontab semantics. Any unrecognized value is treated as `local`, with a warning written to the paniclog. + +
+ +
Fuzz_Seconds
+ +
+ +The maximum random delay, in seconds, applied to the next per-domain mail-queue maintainer firing for any domain currently matched by a *wildcard-binding* rule. Default value is `60`. Set to `0` to disable lift-fuzz entirely. + +This option exists to spread out the "thundering herd" of delivery attempts that would otherwise fire in lock-step on every domain affected by the same window once it closes. While the curfew is engaged, each affected per-domain maintainer is rescheduled to a uniform random offset in `[0, Fuzz_Seconds]` seconds from its next normal tick; over the duration of the window the per-domain cycles drift independently, so deliveries resume gradually after lift instead of all at once. + +Only rules whose binding column is `*` participate in lift-fuzz, because the per-domain maintainer is shared across every binding for a given domain — fuzzing it for a binding-specific rule would unnecessarily slow down deliveries on the bindings that are *not* under curfew. + +The effective spread is capped by the configured [`delayed_queue_scan_interval`](/momentum/4/config/ref-delayed-queue-scan-interval): the mail-queue maintainer refuses to push its next firing further out than the interval that was just scheduled. If `Fuzz_Seconds` is larger than the scan interval, the module logs a warning to make this cap visible to the operator. + +
+ +
+ +### Interaction With Other Suspension Mechanisms + +Curfew is one of several signals that can suspend (binding, domain) delivery; precedence is, from highest to lowest: + +1. An explicit [`suspend_delivery = true`](/momentum/4/config/ref-suspend-delivery) in the configuration scope. Static suspension is evaluated before the curfew hook even runs, so it always wins. + +2. Any Lua policy hook that decides suspension *before* curfew (Lua hooks registered as `_first` run ahead of curfew's hook). Such a hook may short-circuit the chain with its own verdict. + +3. The verdict produced by the curfew schedule. + +4. The verdict produced by the [adaptive](/momentum/4/modules/4-adaptive) module from its cached delivery-history rules. + +In particular, when a Lua hook makes a different decision (for example, force-resuming delivery during a partial-outage drill), curfew defers to that hook for the affected *(binding, domain)* pair. When no other signal speaks up, curfew's schedule is the only signal that matters. + +### Logging and Audit Trail + +Because curfew suspends delivery rather than failing it, there are **no T-record entries** in the mainlog for messages held under a rule — no delivery attempt is made. To make the cause of operator-observed delivery delays attributable to the schedule, the module periodically sweeps every loaded rule and writes a single line to the paniclog whenever a rule transitions between *engaged* and *lifted*: + +``` +curfew: rule #2 engaged (binding=marketing domain=*) +... +curfew: rule #2 lifted (binding=marketing domain=*) +``` + +One sweep runs per [`delayed_queue_scan_interval`](/momentum/4/config/ref-delayed-queue-scan-interval) tick, so the lag between a rule transition and its log line is bounded by the scan interval (typically a few seconds). An operator can also force an immediate sweep on demand — see the `curfew journal` subcommand below. + +#### Note + +The engage/lift transition lines are emitted at the `NOTICE` severity. The default [`debug_level`](/momentum/4/4-module-config) for modules is `ERROR`, so these lines will only reach the paniclog when curfew's `debug_level` is set to `NOTICE` (or a more verbose level — `INFO`, `DEBUG`). For example: + +``` +curfew { + Schedule_File = "/etc/ecelerity/curfew.schedule" + debug_level = "NOTICE" +} +``` + +Without this, curfew still enforces the schedule but the per-rule audit trail is silent. + +### Console Commands + +The following console commands are provided by the curfew module. + +
+ +
curfew reload
+ +
+ +Re-reads the schedule file. This is equivalent to the implicit reload that happens after every `config reload`, but does not require a configuration commit. If the file cannot be opened or contains a parse error, the previously loaded schedule is retained and an error is logged. On success, the response reports the number of rules now active. + +
+ +
curfew show
+ +
+ +Dumps every compiled rule in load order, showing the binding/domain matchers and the time bitmasks decoded back into compact crontab-style ranges. Useful for verifying that a hand-edited schedule file was parsed the way you expected. + +Example output: + +``` +curfew: 2 rule(s), tz=local + [0] binding=* domain=gmail.com minute=* hour=18-23 mday=* month=* wday=1-5 + [1] binding=marketing domain=* minute=* hour=* mday=* month=* wday=0,6 +``` + +
+ +
curfew status
+ +
+ +One-line summary: the number of currently loaded rules, the configured timezone, and the effective `Fuzz_Seconds` value. Useful as a smoke test from monitoring scripts. + +
+ +
curfew fuzz `domain`
+ +
+ +Diagnostic: reports the lift-fuzz value (in seconds) that would currently apply to the named domain. Returns `0` when no wildcard-binding rule is engaged for that domain or when [`Fuzz_Seconds`](#modules.curfew.fuzz_seconds) is set to `0`. Use this to confirm that an expected rule is actually in effect and that lift-fuzz is configured as intended. + +
+ +
curfew journal
+ +
+ +Forces an immediate journal sweep. Any rule whose match verdict has flipped since the last sweep has its engage/lift log line written synchronously, and the count of transitions logged is returned to the console. Mostly useful immediately after editing the schedule (to confirm what state the rules are in *right now*) or as a regression hook in tests. + +
+ +
+ +### Example: Configured Quiet Hours + +The following ecelerity.conf fragment configures the curfew module against the schedule file shown in the previous section, in the local timezone, with a 30-second lift-fuzz spread: + +``` +curfew { + Schedule_File = "/etc/ecelerity/curfew.schedule" + Timezone = "local" + Fuzz_Seconds = 30 +} +``` + +After `config reload`, a follow-up `curfew status` from the console will confirm how many rules were accepted: + +``` +> curfew status +curfew: 3 rule(s) active; tz=local; fuzz=30s +``` diff --git a/content/momentum/4/modules/index.md b/content/momentum/4/modules/index.md index e9a6bad73..246220f08 100644 --- a/content/momentum/4/modules/index.md +++ b/content/momentum/4/modules/index.md @@ -32,6 +32,7 @@ description: "Table of Contents 71 1 Introduction 71 2 ac auth Authentication Ha | [compress_spool](/momentum/4/modules/compress-spool) | Dynamic Spool Compression | | [conntrol](/momentum/4/modules/conntrol) | Fine-Grained Connection Control | | [csapi](/momentum/4/modules/csapi) | Symantec CSAPI Antivirus Support | +| [curfew](/momentum/4/modules/curfew) | Scheduled Delivery Suspension | | [custom_bounce_logger](/momentum/4/modules/custom-bounce-logger) | Custom Bounce Logging | | [custom_logger](/momentum/4/modules/custom-logger) | User-defined Logging | | [dane](/momentum/4/modules/dane) | DANE related DNS Lookups and TLS Verifications | diff --git a/content/momentum/4/modules/summary-all-modules.md b/content/momentum/4/modules/summary-all-modules.md index 93c0aaaf8..cd8f32834 100644 --- a/content/momentum/4/modules/summary-all-modules.md +++ b/content/momentum/4/modules/summary-all-modules.md @@ -36,6 +36,7 @@ All modules are listed alphabetically with a brief description. Singleton module | [“compress_spool – Dynamic Spool Compression”](/momentum/4/modules/compress-spool) (*singleton*) | 4.0 | Compress large messages before writing them to disk |  ✓ |   |   |   | | [“conntrol – Fine-Grained Connection Control”](/momentum/4/modules/conntrol) | 4.0 | Control how inbound connections are established |   |   |  ✓ |   | | [“csapi – Symantec CSAPI Antivirus Support”](/momentum/4/modules/csapi) | 4.0 | Integration for Symantec content scanners |   |   |  ✓ |   | +| [“curfew – Scheduled Delivery Suspension”](/momentum/4/modules/curfew) (*singleton*) | 5.3 | Schedule recurring delivery quiet hours for bindings, binding groups, and domains | ✓ | | | [suspend_delivery](/momentum/4/config/ref-suspend-delivery) | | [“custom_bounce_logger – Custom Bounce Logging”](/momentum/4/modules/custom-bounce-logger) | 4.2 | Append a "User_String" to the end of each bounce record |  ✓ |   |   | [“bounce_logger – Momentum-Style Bounce Logging”](/momentum/4/modules/bounce-logger) | | [“custom_logger – User-defined Logging”](/momentum/4/modules/custom-logger) | 4.0 | Create custom logs |   |   |   |   | | ["dane - DNS-based Authentication of Named Entities"](/momentum/4/modules/dane) | 4.8 | Support for DANE |   |   |   |   | diff --git a/content/momentum/changelog/5/5-3-0.md b/content/momentum/changelog/5/5-3-0.md index fff0a78b3..e1e76e514 100644 --- a/content/momentum/changelog/5/5-3-0.md +++ b/content/momentum/changelog/5/5-3-0.md @@ -14,5 +14,6 @@ This section will list all of the major changes that happened with the release o | Feature | I-1214 | Removed `msys-nodejs` RPM from the Momentum bundle, to be replaced with the 3rd-party `nodejs` package. Node.js LTS 24+ must be installed separately from the system or a vendor repository. | | Feature | I-1216 | Added the [log_hires_timestamp](/momentum/4/config/ref-log-hires-timestamp) option to emit microsecond-resolution timestamps in the `mainlog`, `bouncelog`, `rejectlog`, `paniclog`, custom logs, chunk logs, and message generation logs, preserving event ordering when reading multiple log files together. | | Feature | I-1225 | Added optional `--meta` / `--header` filtering to the [`reroute queue`](/momentum/4/console-commands/reroute-queue#reroute_queue_selective) console command, to selectively move queued messages by metadata or RFC822 header match. | +| Feature | I-1228 | Added the [`curfew`](/momentum/4/modules/curfew) module, scheduling recurring quiet hours during which delivery is suspended for selected bindings, binding groups, or domains. It can replace operator workflows that toggle [`suspend_delivery`](/momentum/4/config/ref-suspend-delivery). | | Feature | TASK-198522 | New DNS configuration options to [rate-limit MX lookups](/momentum/4/config/ref-dns-rate-limit), preventing query bursts from overwhelming the DNS infrastructure. | | Fix | TASK-227757 | [`ha_proxy_client`](/momentum/4/modules/ha-proxy-client) now re-resolves a hostname-based `ha_proxy_server` during each health check, so backend IP changes are picked up automatically without restart. | From 68a41cfb19e73d5dab5e040f1a2a168a265aa522 Mon Sep 17 00:00:00 2001 From: Doug Koerich Date: Thu, 21 May 2026 14:49:39 -0300 Subject: [PATCH 2/4] TASK-144646: Minor changes in the examples (SparkPost/Momentum#1228) Signed-off-by: Doug Koerich --- content/momentum/4/modules/curfew.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/content/momentum/4/modules/curfew.md b/content/momentum/4/modules/curfew.md index 4e79ae13e..9ad132651 100644 --- a/content/momentum/4/modules/curfew.md +++ b/content/momentum/4/modules/curfew.md @@ -35,8 +35,8 @@ That is enough to enable the feature: [`Timezone`](#modules.curfew.timezone) and ``` curfew { Schedule_File = "/etc/ecelerity/curfew.schedule" - Timezone = "local" - Fuzz_Seconds = 60 + Timezone = "UTC" + Fuzz_Seconds = 120 } ``` @@ -259,7 +259,6 @@ The following ecelerity.conf fragment configures the curfew module against the s ``` curfew { Schedule_File = "/etc/ecelerity/curfew.schedule" - Timezone = "local" Fuzz_Seconds = 30 } ``` From 15f51e14a14621c1045c1276821b99d7692c38f0 Mon Sep 17 00:00:00 2001 From: Doug Koerich Date: Fri, 22 May 2026 10:06:05 -0300 Subject: [PATCH 3/4] TASK-144964: Adding note about message expiration during quiet hours (SparkPost/Momentum#1228) Signed-off-by: Doug Koerich --- content/momentum/4/modules/curfew.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/momentum/4/modules/curfew.md b/content/momentum/4/modules/curfew.md index 9ad132651..62319b749 100644 --- a/content/momentum/4/modules/curfew.md +++ b/content/momentum/4/modules/curfew.md @@ -14,6 +14,8 @@ Curfew replaces *ad-hoc* operator workflows that toggle [`suspend_delivery`](/mo Curfew does **not** fail or bounce messages. While a rule is matching, the affected (binding, domain) pairs return suspended verdicts; queued mail simply waits in the messages queue and is retried as soon as the window closes. +However, the global [`message_expiration`](/momentum/4/config/ref-message-expiration) (and the configured [`max_retries`](/momentum/4/config/ref-max-retries)) keeps running while a message waits under curfew. A message whose TTL elapses *during* a quiet hour is treated by Momentum exactly as if it had expired for any other reason: when it reaches the front of the queue after the window lifts, it either makes one final attempt or is failed immediately with "Delivery not attempted (message expired)" — see [`never_attempt_expired_messages`](/momentum/4/config/ref-never-attempt-expired-messages). This is the same outcome a message would get from a long run of transient delivery failures or from an operator-set [`suspend_delivery = true`](/momentum/4/config/ref-suspend-delivery) that outlasts the message's TTL: it is **not** a curfew-specific behaviour, but it is worth sizing quiet hours so that they remain well under the binding/domain's `message_expiration`. + ### Configuration The `curfew` module is a singleton and is loaded automatically when any of its configuration options is set or its console command is invoked. The minimum configuration is a single option — the path to the schedule file: From a6a24fb70421396756d9ea34cdbde404d31011dc Mon Sep 17 00:00:00 2001 From: Doug Koerich Date: Fri, 22 May 2026 10:07:18 -0300 Subject: [PATCH 4/4] TASK-144964: Minor rewording (SparkPost/Momentum#1228) Signed-off-by: Doug Koerich --- content/momentum/4/modules/curfew.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/momentum/4/modules/curfew.md b/content/momentum/4/modules/curfew.md index 62319b749..4b9f472d2 100644 --- a/content/momentum/4/modules/curfew.md +++ b/content/momentum/4/modules/curfew.md @@ -14,7 +14,7 @@ Curfew replaces *ad-hoc* operator workflows that toggle [`suspend_delivery`](/mo Curfew does **not** fail or bounce messages. While a rule is matching, the affected (binding, domain) pairs return suspended verdicts; queued mail simply waits in the messages queue and is retried as soon as the window closes. -However, the global [`message_expiration`](/momentum/4/config/ref-message-expiration) (and the configured [`max_retries`](/momentum/4/config/ref-max-retries)) keeps running while a message waits under curfew. A message whose TTL elapses *during* a quiet hour is treated by Momentum exactly as if it had expired for any other reason: when it reaches the front of the queue after the window lifts, it either makes one final attempt or is failed immediately with "Delivery not attempted (message expired)" — see [`never_attempt_expired_messages`](/momentum/4/config/ref-never-attempt-expired-messages). This is the same outcome a message would get from a long run of transient delivery failures or from an operator-set [`suspend_delivery = true`](/momentum/4/config/ref-suspend-delivery) that outlasts the message's TTL: it is **not** a curfew-specific behaviour, but it is worth sizing quiet hours so that they remain well under the binding/domain's `message_expiration`. +However, the configured [`message_expiration`](/momentum/4/config/ref-message-expiration) (and [`max_retries`](/momentum/4/config/ref-max-retries)) keeps running while a message waits under curfew. A message whose TTL elapses *during* a quiet hour is treated by Momentum exactly as if it had expired for any other reason: when it reaches the front of the queue after the window lifts, it either makes one final attempt or is failed immediately with "Delivery not attempted (message expired)" — see [`never_attempt_expired_messages`](/momentum/4/config/ref-never-attempt-expired-messages). This is the same outcome a message would get from a long run of transient delivery failures or from an operator-set [`suspend_delivery = true`](/momentum/4/config/ref-suspend-delivery) that outlasts the message's TTL: it is **not** a curfew-specific behaviour, but it is worth sizing quiet hours so that they remain well under the binding/domain's `message_expiration`. ### Configuration