Skip to main content
Some flows have to stop and ask somebody. Approve this refund. Confirm this list is the right one. Send me back the spreadsheet. A send-and-wait step does exactly that: it sends the question, parks the run, and continues when the answer arrives — or when the step’s own timeout runs out. Runs are durable, so a step can wait minutes or days without holding anything open and without losing a byte of progress.

One step, not two

A send-and-wait is one task. It is not a send step followed by a separate wait step, and it is not a new execution model — it is the ordinary signal wait the platform already has, with the send composed in front of it and the correlation wiring done for you. Turn it on with wait_for_reply in the task’s execution_config:
That ordering matters and is guaranteed: the platform registers what it is waiting for before the message leaves. A person on a fast mail client can answer in under a second, and a reply that arrives before the platform is listening would simply vanish — nothing would error, the flow would just sit until its timeout.
wait_for_reply is honoured only on a step built to wait. Putting it on an arbitrary action is refused with a 422 naming the field when you save the definition, and refused loudly at run time for definitions saved before that check existed. There is no arm where it is silently ignored — an author who asked for a wait and got a fire-and-forget send would have no indication that the half they cared about never happened.

The v1 boundary

A reply-waiting step must be immediate. Combining wait_for_reply with scheduled or windowed is refused with a 422 naming the field. That is a deliberate boundary rather than an oversight: the correlation record’s deadline is derived from the wait’s own timeout at the moment the task runs, so a task that sat in a window for six hours first would either need its record written before a delay of unknown length, or would silently move its own deadline. Neither is a thing to guess at, and you are owed the answer at save time rather than at 2 a.m. Put the delay in a step before the ask instead.

How a reply finds its way back

Two channels ship, and they correlate a reply differently because the medium gives them different things to work with. Email carries a token on the message itself; SMS has nowhere to put one and matches on the pair of numbers instead. Everything after correlation — the timeout policies, the resumed payload shape, the parked-run status — is identical on both.

Email: a correlation token

Each send carries a fresh, unguessable, single-use correlation token. It is what tells the platform which run, and which step of it, an incoming answer belongs to — and it is what authorises the resume, so it is treated as a credential: it is generated with cryptographic randomness, only its hash is ever stored, and it never appears in a log line. The token travels on two carriers, so a reply that loses one still matches:
Do not edit the tag on the subject line when you reply, and do not strip the reply address. Both carry the identifier that matches your answer to the step waiting for it. A reply that carries neither cannot be matched to anything, and the symptom is not an error — the flow simply waits out its timeout as though nobody answered.If you are writing the message body yourself, say so to the person you are asking. “Reply to this message” is enough; “start a new message to this address” is not.
You supply the mailbox the replies come back to, as reply_to_base — a plain address like requests@reply.example.com. The platform builds the per-send address from it. Do not supply an address that already contains a +.
The reply mailbox needs a domain that actually accepts incoming mail. For the email channel that means an inbound domain bound to the server your credential belongs to, with its MX record published — see receiving replies, and note the warning there about which domain to use. Without it, replies never arrive and every wait ends on its timeout.

SMS replies

A text message has no reply address to decorate and no subject line to tag, so there is nowhere to put a token. An SMS reply is matched instead on the pair of numbers involved — the handset the message went to, and the number it went from — recorded before the message leaves and recomputed from the reply when it arrives. Three consequences follow, and all three are things you can hit on your first attempt: The message must come from one specific number you own. A Messaging Service picks a number from its pool at the moment the message goes out, so a reply would arrive from a number no wait was ever registered against — the flow would sit until its timeout on an answer the person really gave. A reply-waiting step configured with a Messaging Service is refused by name before anything is sent. See both sender forms. One conversation per pair of numbers at a time. If a wait is already outstanding between those two numbers, a second reply-waiting step between the same pair is refused — immediately, by name, and before any message goes out, so it costs no text and no budget. The alternative was letting the newer step take over the pair, which would silently steal the first flow’s answer: the first flow has already sent its message and is sitting in its wait, and it would then time out on a reply the human actually sent. A loud refusal for one author beats an undiagnosable silence for two. If you need to ask the same person two things at once, ask them from two different numbers, or sequence the steps. Keep the wait short. People answer a text in minutes and abandon it in hours, so an email-shaped timeout: "24h" mostly buys dead flows — and because the correlation key is the number pair, a long wait is also a long window in which a second flow reaching that person is refused.
There is no MX record and no inbound domain to configure for SMS. Replies arrive on the number itself. What the platform needs instead is your Twilio account’s own auth token, because that is what Twilio signs its inbound webhooks with — see connecting Twilio for the mistake that quietly breaks this.

Who is allowed to answer

Two conditions, and the first is the one doing the real work:
  1. The reply must carry the correlation token. It is unguessable and single-use — the first valid answer consumes it, so a second copy of the same reply cannot resume the same step twice.
  2. By default, the reply must come from the address the message was sent to. Defence in depth: an answer that carries a valid token but arrives from somewhere else is not acted on.
A reply resumes the parked task exactly once. The token is consumed by the first valid answer, so a second copy of the same reply — a mail server retrying, a person hitting Reply twice, the provider redelivering after a lost acknowledgement — cannot resume the step again. Deliveries are recorded as they are handled, so a redelivery of a reply that was already acted on is recognised and produces no second resume. A redelivery of one that was not fully handled still converges on exactly one. An answer that matches nothing is accepted and dropped, not reported as an error. A reply carrying no recognisable token, a token for a wait that has already timed out, or a sender that does not match, is acknowledged to the mail provider and goes no further. It has to be: reporting the difference would let anyone with the reply address probe which tokens are live, and refusing it would make the provider retry a message nothing will ever match. The flow is unaffected — from its point of view nobody answered. On SMS the two conditions collapse into one. The pair of numbers is the correlation and the authorisation at the same time: a reply that correlates has, by construction, come from the handset the message was sent to. sender_matched is still in the resumed payload, and it still reads true. Some workflows need the second condition relaxed — the person you asked forwards it to a colleague, or a shared mailbox answers on their behalf. Set sender_match: false in the step’s execution_config to accept any sender that carries the token.
Relaxing it does not hide anything: sender_matched is in the resumed payload either way, so flow logic can always branch on whether the answer came from the person who was asked.

What lands in context on resume

When the answer arrives, the step’s output is a normalized payload — the same shape regardless of which channel the reply came back on. Provider-specific data never reaches your flow: Read them like any other step output:
Keeping this shape platform-neutral is the point: a flow authored against an email reply keeps working unchanged when the same wait is later answered over another channel.

Attachments arrive as file references

An answer carrying a file gives you a reference to it, never its contents: The attachment’s bytes are decoded, hashed and stored at the moment the reply arrives, before the flow is woken. So by the time a later step reads the reference, the file already exists — there is nothing to fetch, and no third-party address to re-visit. Bytes never ride the resume. They would make the payload grow with the file, and a payload that can grow with its attachment is one that eventually fails the very resume it exists to carry. A payload limit here is a hard error, not a slow path. Read the reference the same way you read any other stored file:
  • To turn a CSV into records, hand that value to getdialed__records__parse_csv as its file_id. The contents are read inside the step and never travel through the flow.
  • To work on the text itself, use getdialed__files__read_text first, then bind its text output. It is capped at 512 KB and refuses rather than truncates — silently short text reads as complete everywhere afterwards.
The send-and-wait guide walks both shapes end to end.
A binary attachment is stored, and reading it as text is refused. A PDF, a spreadsheet in its own format or an image arrives with a file_id like anything else — the platform stores whatever was sent. What is refused is reading it as text, by the step that does the reading, because a step that decoded binary into characters would hand you a string of replacement characters that looked exactly like a successful read. The reference is still there, so flow logic can see that the file arrived and say something useful about it.
A stored attachment is kept for 30 days. Inbound attachments are not permanent: they expire on a fixed horizon rather than living until deleted, because they arrive on a path anybody with the reply address can reach and unbounded storage there is not a thing to offer. A flow that needs a copy for longer should write one out with file steps while the reference is still live.

When nobody answers

The wait ends on the step’s timeout, and on_timeout says what happens next. These are the ordinary signaled-task timeout policies — a send-and-wait inherits them unchanged: continue with a meaningful on_timeout_output is usually what you want, because it turns “nobody answered” into a value the rest of the flow can act on:
A failed send never enters the wait. If the message could not be sent, the step ends there rather than parking for hours on a reply to something that never went out. That failure mode is otherwise invisible: nothing errors, the flow simply sits.

When the message never arrives

A send can succeed and the message can still never reach the person — a carrier filters it, the handset is unreachable, the sending number is not registered. When the provider reports that, the wait does not sit through it. A terminal delivery failure ends the wait immediately, with delivery_failed: true and a named failure_reason naming the provider’s status and error code. The step is treated as failed rather than answered, subject to its on_failure policy — which is the point: without it, a flow would park for its full timeout on somebody who was never texted, and “the handset is unreachable” and “the human declined to answer” would be the same silence. A successful delivery deliberately does not end the wait. Delivery is not an answer — unparking on it would end every conversation the instant the handset received the question.
On SMS, this does not fire today, and the reason is a genuine collision between two constraints. Twilio reports delivery outcomes only for messages sent through a Messaging Service — a plain phone number has no such setting — and a reply-waiting step cannot use a Messaging Service, because the pool would pick a number no wait could be correlated against.So a reply-waiting text gets inbound replies but no delivery outcome, and a message the carrier drops ends the wait on its timeout rather than with a named failure. Set a timeout you are happy to actually wait, and use on_timeout_output to say what “no answer” means for your flow — see when nobody answers.Email is unaffected: it has no equivalent constraint on its sending address.

Reading a parked run

While a step is waiting, its execution reports the status waiting — a non-terminal state meaning parked on an answer, not stuck and not finished. It returns to running the moment the wait ends, whichever way it ends. Watching the stream shows the same thing as it happens: an execution.waiting frame when the step parks and an execution.resumed frame when it continues. Neither is terminal, so a park never closes an open stream — a detail view can sit on a run that is waiting for a day and pick the story back up when somebody answers.

Next steps

Send and wait for a reply

The end-to-end walkthrough: ask for a CSV, wait, parse it, fan out.

Transactional email

One of the two channels — sending domains, suppression, and what a send reports.

SMS

The other — both sender forms, strict E.164 recipients, and why acceptance is not delivery.

Execution models

The signaled wait a send-and-wait is built on, and its timeout policies.

Execution streaming

Watch a run park and resume live, without polling.