Programmatic Graph workflows
A workflow combines dependent Graph operations in one process. Complete CLI setup first. Use designated mailboxes because these workflows send and modify real messages.
Send, wait, and move
Run from Architecture/GraphSyncExperimenter/cli-graph/:
bun run workflow:send-wait-move -- \
--sender-user-id sender@example.com \
--recipient-user-id recipient@example.com \
--recipient-address recipient@example.com \
--target-folder-name "Graph workflow target" \
--timeout-ms 120000
The workflow sends a unique message and waits for the recipient copy. It then moves that copy to the target folder. A timeout does not prove that sending failed. Check Sent before another send.
Mark read and add a category
bun run workflow:mark-read-add-label -- \
--user-id person@example.com \
--immutable-email-id IMMUTABLE_ID \
--label-name "Graph Tested"
The message must belong to the selected mailbox. Inspect returned JSON and the process exit code. See outputs.
Typed references
Use an explicit folder ID when available:
await toolkit.email.moveEmail(
userId,
immutableEmailId,
{ id: "AAMkAG..." },
);
A unique exact folder name also works:
await toolkit.email.moveEmail(
userId,
immutableEmailId,
{ name: "Graph Tests" },
);
Categories accept their unique names:
await toolkit.email.addLabel(
userId,
immutableEmailId,
{ name: "Graph Tested" },
);
Name matching ignores case and requires one exact result. An ambiguous name needs an ID.
Cross-mailbox identity
Never use the sender copy's immutable ID for the recipient mailbox. Correlate with internetMessageId, then use the recipient result's emailId. Set a finite wait timeout, such as --timeout-ms 120000.
See CLI wait options for polling behavior. The project's workflows/README.md describes the bundled workflows. Permanent deletion requires an explicit --confirm in the relevant CLI command. It is not a dry-run option.