EventBridge schedules
Fourteen schedules drive the platform. Each is an EventBridge rule declared as an event on a
function in lambdas/template.yaml, and each carries a static Input JSON document that
becomes the entire event the handler receives.
| Field | Role |
|---|---|
|
The routing key. The function’s |
|
Origin label, for readability and CloudWatch filtering. |
|
Usually |
Because the Input is static, a schedule is a trigger, never a parameter carrier. Anything a
handler needs to know it looks up: the watermark from configs, the pending rows from
PostgreSQL, the segment from BigQuery.
|
The |
morpheus-hubspot
Ten of the fourteen. Rule names are prefixed morpheus-sam-.
| Rule | Expression | detail-type |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The two cron(0 9,20 * * ? *) jobs run at 09:00 and 20:00 UTC — before and after the US
business day, which is when the upstream exports have settled.
The four re-engagement schedules share a rate(6 hours) cadence and therefore fire together.
That simultaneity is why the round-robin cursor is taken under an advisory lock: four handlers
assigning reps at the same instant would otherwise all read the same cursor value. See
Ownership and round-robin.
The one ScheduleV2
SalesRepCallListRefresh is the only rule declared as Type: ScheduleV2 rather than
Type: Schedule, because it is the only one that needs ScheduleExpressionTimezone. It runs
in America/New_York so the list rebuild tracks the sales team’s working day across daylight
saving transitions, which a UTC cron cannot do.
The production expression cron(0 6 * * ? *) — a single daily rebuild at 06:00 Eastern — is
present but commented out in favour of rate(6 hours). See
Daily call lists.
phone-agent
| Rule | Expression | detail-type |
|---|---|---|
|
|
|
|
|
|
|
|
|
phone-line-intel-twice-daily is named for a cadence it no longer runs — the commented
production expression is cron(0 6,18 * * ? *), but the deployed one is rate(3 hours),
eight times a day. Each run processes up to PHONE_AGENT_BATCH_SIZE (25) numbers through
Twilio Lookup, so the deployed rate spends roughly four times the intended quota. See
Phone line intelligence.
task-deadline-reminder at 08:00, 14:00, and 20:00 UTC drives escalation: each run finds
PENDING tasks past due with escalationStage < 3 and notifies Slack. Three runs a day
against a cap of three stages means a stale task is escalated to exhaustion within 24 hours.
See Follow-up tasks and escalation.
campaign-reengagement carries a commented production expression of cron(0 12 * * ? *).
enrichment-scraper
| Rule | Expression | detail-type |
|---|---|---|
|
|
|
The scraper’s discovery schedules are commented out:
| Rule | Expression | detail-type |
|---|---|---|
|
|
|
|
|
also |
The Amazon rule’s detail carries {"marketplace":"amazon","provider":"smartscout"} — one of
the few schedules that does parameterise its handler.
|
With both discovery schedules disabled, no new sellers enter the funnel. The scraper’s only live job is mirroring HubSpot contacts from BigQuery into PostgreSQL. Together with the disabled scorer triggers, the entire discovery-and-enrichment half of the pipeline is dormant; the re-engagement and CRM half is not. See Outbound funnel. |
enrichment-scorer
No active schedules. Both backfills are commented out:
| Rule | Expression | detail-type |
|---|---|---|
|
|
|
|
|
|
rate(4 minutes) is a development value — at 10 sellers per run it would process 3,600 a day
and hold a Bedrock call open most of the time. Re-enabling it as written would be expensive;
the rate(60 hours) enrichment backfill is the more plausible production cadence of the two.
Reading the expressions
rate(n units) measures from the previous invocation’s completion, so a rate(6 hours) job
that takes ten minutes runs every six hours and ten minutes and drifts. cron(…) fires at
fixed wall-clock times and does not drift.
The distinction matters where two jobs must not overlap. The four re-engagement handlers are
all rate(6 hours) and drift together, which is fine because each is independently guarded;
the two cron(0 9,20 * * ? *) syncs are pinned because they read a shared watermark window.
All cron(…) expressions are UTC unless the rule sets ScheduleExpressionTimezone, which
only SalesRepCallListRefresh does.
Changing a schedule
-
Edit the expression in
lambdas/template.yaml. -
If the
detail-typechanges, update the matchingregistry.register(…)call — nothing validates the pair. -
make deploy-lambdas. -
Update this page and Scheduled events in the same pull request.
Disabling a schedule without a deploy is possible from the EventBridge console, but the change
is invisible in the repository. For the scraper there is a better lever: the
enrichment.<marketplace>.disabled key in the configs table, which the handler checks on
every run. See Runtime state in configs.