- Ask — email the owner requesting this week’s list, and pause.
- Parse — turn the CSV they attached into a record set, straight from the stored file.
- Hand off — start the flow that processes the list.
You’ll need an API key (see Authentication) and a Postmark credential (see transactional email). Export both:
1
Ask, and wait
The whole feature is one task. The parameters this step takes are the same set the plain Send Email step takes, with one substitution:
wait_for_reply in the execution_config turns the send into a send-and-wait; reply_to_base is the mailbox answers come back to, and the platform builds a unique per-send reply address from it so the answer can be matched to this exact step.There is deliberately no
reply_to here. The plain send has one; this step writes it itself, because it is the address the wait is listening on. Two answers to “where do replies go” would mean the first author to set one silently disabled the wait.What each part of the execution_config does:Ask the person to reply rather than to compose a new message, and to leave the subject alone. Both the reply address and the subject tag carry the identifier that matches their answer to this step, and a reply carrying neither cannot be matched — the flow just waits out its timeout as though nobody wrote back.
2
Guard the no-answer path
on_timeout: continue means the flow proceeds either way, so say what happens when nobody answered before you start reading an attachment that is not there:answered is there because on_timeout_output put it there — it exists only on the timeout path. The resume payload carries no such field, so the condition is false whenever somebody actually replied.3
Parse the attachment into a record set
When the reply arrives, each attached file is stored and the step’s output carries a file reference for it — never the bytes:Bytes never ride the resume, and that is structural rather than a tuning choice: a resume payload is persisted as workflow history, and history has a hard payload error ceiling rather than a slow path. A payload that grows with the attachment is a payload that eventually fails the very resume it exists to carry. So the bytes are written to storage at the moment the reply arrives, and what travels is an id.A set of zero records is never created, so It returns
getdialed__records__parse_csv takes that id directly and produces a record set. The file’s contents are read inside the step and never enter the flow:Supply either
file_id or text — never both. They are the two doors onto the same parser. Supplying neither is refused by name, and so is supplying both: an author who bound both would otherwise get a set built from whichever input the step happened to prefer, with the other one looking as though it had been honoured.record_set_id is empty when the file held nothing. Guard on row_count rather than assuming a set exists.parse_csv reads up to 1 MB in this mode, refuses a file that is not UTF-8 text, and refuses a file that is not one of yours. A file too large is refused rather than partly read — a truncated CSV parses cleanly and produces a silently short record set, which is the worst available outcome here.If you want the text itself
Parsing straight from the file is the shortest correct chain, and for a CSV it is the one to use. Where the flow needs the contents — to branch on them, to pass them to an HTTP step, to put them in another message — insertgetdialed__files__read_text between the two steps and bind its output instead:text, size_bytes, sha256, content_type, filename and truncated. Then bind "text": "{{ step_read.output.text }}" on the parse step instead of file_id.Two limits to know before you choose this shape. read_text reads files up to 512 KB — smaller than parse_csv’s own 1 MB, because this text has to travel through the flow and parse_csv’s does not. And it refuses rather than truncates: a file over the cap fails the step by name, and truncated is always false on a successful read. A file that is not valid UTF-8 is refused too, rather than handed back as replacement characters that would read as a successful parse.4
Hand the set to the flow that does the work
getdialed__utils__trigger_flow starts another definition with the set. bulk gives that flow the whole list in one run; per_record starts one run per row.5
Save it and trigger it
Create the definition with the steps in order:Then trigger it:
6
Watch it park and resume
Read the execution while the ask step is waiting:
waiting means parked on an answer — non-terminal, and not stuck. It returns to running the moment the wait ends, whichever way it ends. started_at keeps pointing at the real start of the run, so a two-day wait reads as a two-day run rather than a two-second one.To watch it live instead of polling, stream the execution: you will see an execution.waiting frame when it parks and an execution.resumed frame when it continues. Neither closes the stream.7
Reply, and read what came back
Reply to the message — from the address it was sent to, with the CSV attached, leaving the subject as it is. Within moments the run resumes, and the ask step’s output is the normalized reply:
sender_matched is reported whether or not you required it, so a flow that loosened the rule can still branch on who actually answered.The sha256 on a stored attachment is ours: it was computed over the bytes as they were written, not copied from anything the sender claimed. Anything that reads the same file back can compute the same value.What changed
An earlier version of this guide showed a four-step chain with a fetch-the-attachment step in the middle, which read the file out of the provider’s storage before the parse. That step is gone from this chain, and the reason is worth stating rather than quietly dropping:- Attachment bytes are now stored at the moment the reply arrives, by the platform, into the platform’s own file store. There is nothing left to fetch — the file already exists before the flow wakes up.
- The reply’s
attachmentsentries therefore carry afile_id, which every file-taking step accepts, instead of a provider address that only one provider-specific step could use. - The old chain also depended on the provider publishing a content hash for the attachment, and refused without one. That refusal was correct — a download that cannot be verified should not happen — but it meant the chain could not complete at all where the hash was absent.
{{ <ask-step>.output.attachments[0].file_id }}. If that flow needs the text rather than the records, replace the fetch step with getdialed__files__read_text on the same file_id, as shown above.
When it does not resume
Almost every “the reply did nothing” report comes down to one of these:Next steps
Human in the loop
The full model: correlation, authorization, timeouts and the resume payload.
Transactional email
Sending domains, suppression outcomes and what the send step reports.
Files
File references, the read step, and how long a stored attachment is kept.
Record transforms
Clean, filter and reshape the set before handing it on.