Skip to main content

Weekly digest email with a screenshot

This example builds a workflow: a short list of steps Studio runs in the background on a schedule, keeping each step's output between runs. This one reads last week's failed orders, summarises them by reason, asks a model to write a short digest, takes a screenshot of an app that shows the same numbers, and emails the digest, with the screenshot attached, to your operations address. Nothing goes out until you approve it in Studio.

What you will have at the end​

Every Monday at 08:00 in your timezone (this page uses Asia/Kolkata), an email waits under Approvals in Studio:

Subject: Failed orders last week: card declines lead (412 of 611)

  • 611 orders failed in the week ending 21 September, 412 of them card declines.
  • Out-of-stock failures fell to 98 from 140 the week before.
  • ...

Attached: a screenshot of Test Digest app, showing the same table.

You open it, see the summary table it was written from, and press Approve; then it is sent. If a week has no failed orders, nothing is written, nothing is captured, and nothing is sent.

What the agent will ask you before it builds​

  1. Who gets the email, and when? The address (on preview, only an address the deployment allows; this page writes it as ops@yourcompany.com), the day, the time and the timezone.
  2. Do you want to approve each email, or let it go out on its own? This example approves each one. Approval is per email and is bound to the exact text you saw.
  3. What should the digest say? By reason, by region, top five? The agent proposes "failed orders by reason, top reasons first, with the week-over-week change" and shows you the summary table for last week before it writes the prompt: "Last week: card_declined 412, out_of_stock 98, address_invalid 61, other 40; 611 in all. Does that match your weekly report?"
  4. Which app should be in the screenshot? The screenshot step can only capture an app that is shared with the whole workspace, and it must never capture someone else's app. So the agent builds a small new app, Test Digest app, for you to share. If you already have an app you own that shows the right numbers, you can share that instead; the agent will not touch an app it did not create.
  5. A safety note the agent should make unprompted. The digest text is written by a model. It is told to quote the numbers exactly and not to invent causes, and you approve every email, so nothing a model wrote reaches anyone without a person reading it.

The pieces it creates​

PieceNameWhat it is
AppTest Digest appA small composed app (no code) showing last week's failed orders by reason
WorkflowWeekly failed orders digestFive steps: query, sql, llm, snapshot, send
Workflow filessql/summary.sql, prompts/digest.mdThe summary SQL and the digest prompt, inside the workflow
Scheduletrigger weekly, 0 8 * * 1 in Asia/KolkataPart of the workflow document

Steps, in order​

1. Build the digest app (agent), then share it: Only a person​

The agent composes Test Digest app from a spec with the design tools (design_model_card, design_compose) and publishes it (Your call: it asks first). Then:

Only a person: in Studio, open the app and share it with everyone in the workspace. The screenshot step runs as Studio's own service identity, which holds no personal grants, so it can only see an app shared with the whole workspace. Until you share it, the workflow's plan warns snapshot_app_not_shared and a run fails that step before taking any picture.

2. Write the workflow (agent)​

The agent reads the workflow guide and the list of step kinds (workflow_kinds) first, then creates the workflow with workflow_create, passing the document, the two files, your model and timezone: "Asia/Kolkata". The document:

{
"schema": "bicycle.workflow/v1",
"title": "Weekly failed orders digest",
"queries": {
"failed_orders": {
"sql": "SELECT day, reason, failed_orders FROM orders WHERE day >= $from AND day < $to ORDER BY day",
"parameters": [{"name": "from", "type": "date"}, {"name": "to", "type": "date"}],
"columns": [{"name": "day", "type": "date"}, {"name": "reason", "type": "string"},
{"name": "failed_orders", "type": "number"}],
"maxLimit": 5000
}
},
"artifacts": {
"failed": {"type": "table"},
"summary": {"type": "table"},
"digest": {"type": "message", "channel": "email"},
"screenshot": {"type": "report"},
"digest_receipt": {"type": "receipt"}
},
"nodes": {
"fetch": {"kind": "query",
"config": {"query": "failed_orders",
"params": {"from": "${run.logical_date - P7D}", "to": "${run.logical_date + P1D}"}},
"outputs": {"rows": "failed"}},
"summarise": {"kind": "sql", "config": {"file": "sql/summary.sql"},
"inputs": {"failed": "failed"}, "outputs": {"summary": "summary"}},
"write": {"kind": "llm", "when": "inputs.summary.rows > 0",
"config": {"prompt": "prompts/digest.md", "effort": "low", "max_output_tokens": 800,
"max_cost_usd": 0.05, "output": {"schema": "builtin:message"}},
"inputs": {"summary": "summary"}, "outputs": {"digest": "digest"}},
"snap": {"kind": "snapshot", "when": "inputs.summary.rows > 0",
"config": {"app": "<app id of Test Digest app>", "tabs": ["default"]},
"inputs": {"summary": "summary"}, "outputs": {"report": "screenshot"}},
"send": {"kind": "action", "when": "inputs.summary.rows > 0",
"config": {"to": {"channel": "email", "recipients": ["ops@yourcompany.com"]},
"payload": "digest", "idempotency_key": "failed-orders-${run.logical_date}",
"max_sends_per_run": 1,
"approval": {"mode": "manual", "approvers": {"roles": ["workflow_owner"]},
"timeout": "P1D", "on_timeout": "reject", "review": ["summary"]}},
"inputs": {"digest": "digest", "summary": "summary", "screenshot": "screenshot"},
"outputs": {"receipt": "digest_receipt"}}
},
"triggers": {"weekly": {"type": "cron", "cron": "0 8 * * 1", "targets": ["digest_receipt"]},
"manual": {"type": "manual"}}
}
-- sql/summary.sql
SELECT reason, sum(failed_orders) AS failed_orders
FROM failed
GROUP BY reason
ORDER BY failed_orders DESC
<!-- prompts/digest.md -->
Write a short weekly digest of failed orders for the operations team, for the week ending {{ run.logical_date }}.

{{ inputs.summary.rows | tojson(indent=2) }}

- `subject`: under 80 characters, naming the biggest reason.
- `body`: Markdown, at most five bullets. Quote the numbers exactly; do not invent causes.

Reading it in words:

  • fetch runs the declared query for the seven days before the run's date. ${run.logical_date} is the date the run is for (the Monday); a workflow never reads the clock.
  • summarise groups the rows by reason. It is plain SQL over the previous step's table, with no network and no files.
  • write asks a model for a subject and a body, in a fixed shape (builtin:message), capped at five cents.
  • snap takes a PNG of the digest app's default tab.
  • send emails the digest to your address, with the screenshot attached and the summary table shown under the message, after a person approves. review: ["summary"] means the approver sees the summary table next to the text. idempotency_key means one email per week's date, ever: a re-run for the same Monday cannot send twice.
  • Every step after summarise has when: "inputs.summary.rows > 0", so an empty week sends nothing.

If you had wanted a Slack message or a ticket instead of an email, to would name one of your workspace's actions (channel: "action"). Every action send needs a person's approval unless a tenant admin has allowed that action to run on its own; a step set to auto on such an action is refused with action_requires_approval, and its suggested fix switches it to manual.

3. Check, plan, try run (agent)​

The agent uses workflow_patch with dry_run: true until the document is valid (every error comes with a one-line fix), then workflow_plan. The plan shows what would run, the estimated cost, the one send it would open, and the gated changes: the send step is new, so a person must publish.

Then a try run: workflow_run with the draft revision. On a try run every step really runs, but the send only dry-runs: no email, no approval request. workflow_run_describe shows every step's outcome and row counts, and the screenshot was taken. The agent shows you the digest text it produced and asks whether it reads right. Change the prompt now if not; it is cheap here.

4. Publish the workflow: Only a person​

workflow_publish refuses the revision because it contains a gated change (a new send step). The agent gives you the link to the workflow's page.

Only a person: open it, read the send step (recipient, approval, the model-written text), and publish. From now on the schedule uses this published revision.

5. A real run and the first approval: Only a person​

The agent starts one real run (workflow_run on the published revision) so you do not wait until Monday. This time the send step opens an approval request. The agent can describe the run and tell you an approval is waiting, but reading and approving it are yours (the agent's tool for listing approvals is landing).

Only a person: in Studio, open Approvals. Read the subject, the body and the summary table beside it. Check the numbers against the sample from question 3. Approve. The receipt's state becomes sent and one email arrives at ops@yourcompany.com. Reject instead, and nothing goes out; the run is recorded as rejected.

How to test it​

  1. The empty-week guard. Ask the agent for a try run with a logical_date in a week you know had no failed orders (or before the model's data starts). workflow_run_describe should show fetch and summarise done, and write, snap and send skipped. Nothing was written or captured.
  2. The screenshot. In Studio, open the workflow's run and look at the screenshot artifact: the PNG should show the digest app with last week's numbers, the same as the summary table. If the step failed with snapshot_app_not_shared, the app is not shared with the whole workspace yet; if snapshot_render_failed, the app showed an error page. A failed screenshot always means no email: the send is skipped, it never falls back to an old picture and never goes without one.
  3. The approval. After step 5 above, check the email arrived, with the screenshot attached and the table under the message.
  4. The ledger. Ask the agent to run the published workflow again for the same date. It joins the earlier run: no second email, because the idempotency key was already sent.
  5. Runs. Every run is on the Runs page: the run itself, and one entry per step. A run whose email is waiting shows Waiting for approval; a run where the screenshot failed shows Partly done with the failed step named.

How to switch it off​

  • Pause the schedule once you have seen it work, so it does not email you every Monday while you are still learning: in Studio, open the workflow and choose Pause on the weekly trigger. Resume is next to it. Pausing does not need a publish. (The agent's tool for pausing is landing; today it will tell you to do it in Studio.)
  • Disable the workflow to stop it for good: in Studio, on the workflow's page (the creator or an admin). Disabling deletes its schedules, cancels waiting approvals and queued runs, and refuses new runs; nothing is hard-deleted.
  • Unshare or unpublish Test Digest app if it was only for this. Unsharing it means any future screenshot step fails, and therefore no future email; that is by design.

What you learned​

  • A workflow is steps with when guards; an empty input means the steps after it do not run.
  • Try runs cost little and send nothing; use them until the text reads right.
  • A new send step is a gated change: a person publishes it, and a person approves each email.
  • A screenshot needs an app shared with the whole workspace; a failed screenshot sends nothing.
  • The idempotency key means one email per date, ever.

Read more: workflow steps, sends and approvals, schedules and failures, publishing and sharing an app.

Next: Train a model weekly, predict daily.

Copy for your agent
Build me a Bicycle Studio workflow on preview that emails ops@yourcompany.com every Monday at 08:00
Asia/Kolkata with a digest of last week's failed orders by reason, plus a screenshot of an app
showing the same numbers. I want to approve each email before it goes out. An empty week must
send nothing.

How to work with me:
- Read the workflow guide and workflow_kinds first. Use query -> sql -> llm -> snapshot -> send,
with "when" guards on everything after the summary.
- Before you build, confirm with me the recipient, the day and time, and what the digest should
say. Run the query and show me last week's summary table to check against my weekly report.
- Create a new app "Test Digest app" for the screenshot (composed from a spec is fine), publish it
after I say yes, then ask me to share it with the whole workspace, and say why the snapshot step
needs that. Never point the snapshot at any other app.
- The send step: to email with recipients ["ops@yourcompany.com"], approval mode manual,
idempotency key with ${run.logical_date}, max_sends_per_run 1. No Slack, Jira or other actions.
- workflow_create with timezone Asia/Kolkata and the cron in the document; workflow_patch with
dry_run until valid; workflow_plan; then a try run of the draft and show me the digest text.
- When workflow_publish is refused as gated, give me the link and wait. After I publish, start one
real run and tell me an approval is waiting under Approvals; do not try to approve it.
- At the end, tell me how to pause the weekly trigger in Studio, and how to disable everything.
- Only create things titled "Test ...". Never change an existing app.