Browse the docs

Reference

Durations

One form, seven units, and two of them are nominal.

A duration is an amount and a unit suffix, with no space between them.

SendQLSyntax
30d    12h    2w    6mo    1y    90s    5m

It is written identically everywhere — in a workflow’s schedule, in a SendQL window, in an age comparison. There is one form and there are no alternatives.

SendFlowSyntax
wait 2d                                    // a schedule
frequency cap 4 per 7d                     // a schedule
repeat up to 3 every 24h { ... }           // a schedule
if count(open within 7d) >= 1 { ... }      // inside a predicate
if now - attr.signup_date > 30d { ... }    // inside a predicate

The units

UnitMeansExact length
sseconds1 second
mminutes60 seconds
hhours60 minutes
ddays24 hours
wweeks7 days
momonths30 days — nominal
yyears365 days — nominal

m is minutes. Months are mo.

This is the one genuinely dangerous ambiguity in the language, and it is worth saying loudly:

SendQLSyntax
5m     // five minutes
5mo    // five months

A wait 3m that was meant to be wait 3mo does not fail — it succeeds, three minutes later. The lexer checks for the mo suffix first and falls back to the single trailing character, so both spellings are valid and only one of them is what you meant. There is no way for the language to catch this for you. Read your diffs.

Months and years are nominal

1mo is exactly 30 days. 1y is exactly 365 days. Neither is calendar-aware: 1mo from the 31st of January is not the 28th of February, it is the 2nd of March.

This is deliberate, and the reason is worth understanding because it explains a lot about the rest of the design. A calendar-exact month would make a literal’s meaning depend on when it was evaluated1mo would be 28 days in February and 31 in March. That would make it impossible to check a between window for reversal statically, and impossible for the engine to fold a schedule at compile time.

A duration has to be a fixed quantity for the language to be able to reason about it at all. So it is one.

If you need calendar-exact behaviour — “the first of every month” — that is a date on the contact, and it belongs in a date-relative trigger, not in a duration.

What is not a duration

Not thisWhy
30 daysThe word form is not a duration. Write 30d.
30 dA duration is one token; no space.
-1dDurations are unsigned. Direction comes from the operator.
2.5dThe amount is an integer. Write 60h.
0dValid inside a predicate, but a schedule duration must be at least 1.

The word form is the one people reach for first, and it fails with an error that hands you the fix rather than pointing at a token:

SendFlowRejected
workflow "t" v1 {
  enter on segment "trial-started"

  wait 2 days
}

durations are written in the compact form: use 2d, not 2 days (a workflow and a SendQL condition spell a duration the same way now)

The compact form is the only form, everywhere. A workflow’s schedule and a condition inside that workflow spell a duration exactly the same way, so a single line never carries two spellings of one idea:

SendFlowSyntax
wait up to 7d until count(activity.login within 7d) >= 2

Get the unit wrong and it tells you that too:

SendFlowRejected
workflow "t" v1 {
  enter on segment "trial-started"

  wait 2 dayz
}

expected a duration such as 7d, 24h or 2w — "dayz" is not a unit

Rounding and precision

0d is rejected as a schedulewait 0d, every 0h and frequency cap 4 per 0d are all errors, because each describes something that would either never happen or happen infinitely. Inside a predicate, a zero duration is merely pointless rather than wrong, and is left alone.

A schedule duration over a year draws a warning (wait up to 400d is a very long wait window), on the theory that it is more often a typo than an intention.