Scheduled CRM workflows
Choose On a schedule in CRM → Automations. Save the workflow, reopen it, then set a schedule and audience. New workflows can stay stopped and in dry run while configured. A workflow without a schedule shows Needs a schedule and never fires. The panel previews five server-computed ISO instants and local times, and displays validation refusals unchanged.
The five cron fields are minute, hour, day of month, month, and weekday. They support *, comma lists, inclusive ranges, and /step. Months accept JAN–DEC; weekdays accept SUN–SAT, with 0 and 7 both Sunday. When both day fields are restricted they are ORed. Following Vixie cron, a day field beginning with * (including */1 or */2) instead requires both day fields to match; 0 9 */1 * MON runs only on Mondays, and 0 9 */2 * MON only on Mondays with an odd day of month. An explicit range such as 1-31 remains restricted. Expressions must fire within 366 days and have no occurrences closer than five minutes in the next seven days. Timezones are validated against the server's IANA database.
The timezone defaults to the organization's dashboard timezone, or UTC if unset. Local times remain local across DST: missing spring times shift forward by the gap (Toronto 02:30 becomes 03:30), and repeated autumn times fire once at the first instant. Equal shifted instants form one occurrence. This DST behavior is the product contract and deliberately differs from Unix cron's missing/repeated-time handling.
The existing automation wait tick also evaluates schedules. After downtime, only the latest due occurrence fires; older ones are counted in missedBefore. Claim insertion and schedule advance commit together. A persisted occurrence resumes with ordered batches of 200 live contacts, companies, or deals, optionally filtered by a tenant tag. Record-event conditions apply through the normal engine. Each event identifies exactly one workflow, record and occurrence, with the engine's existing durable dedupe, current author authority, hourly caps, and dry-run behavior.
An occurrence processes at most 2,000 records. Remaining audience records are counted as skipped with audience_limit. Counts distinguish engine admissions from skipped records; admitted runs can still fail in the engine, and org caps can skip many of the first 2,000. Occurrences older than 24 hours fail with stale_occurrence. Editing, stopping or changing the schedule abandons unfinished fanout, recomputes the next run, and never resumes an old revision. Already committed effects remain in run history.
API: GET/PUT /v1/orgs/:orgId/automations/:workflowId/schedule, POST the same path plus /preview, and GET plus /occurrences?limit=20. PUT accepts { cron, timezone, audience: { kind, tagId? } }. Read needs crm:read; PUT needs crm:edit. Existing automation feature, credential, tenant and authentication gates apply. A non-schedule workflow returns 409 trigger_mismatch. MCP/QAC tools automation_schedule_set and automation_schedule_get invoke the same internal routes. Schedule triggers cannot use webhook_out or lifecycle_actions, which require other event contexts.
Registration is delivered as literal parent-file hunks in docs/ops/workflow-schedule-trigger-20261022/registration.patch, including additive migration 13519. The author worktree intentionally leaves shared production parents unchanged; ROOT must apply the proposal before this feature is available.
References checked 2026-10-22: cron field semantics, PostgreSQL row locking and SKIP LOCKED. The requested DST behavior takes precedence over Unix cron.
Wildcard day-field behavior checked 2026-10-23 against the Cronie parser and day matching.