{"projects":[{"id":9,"slug":"miro","name":"Miro","summary":"Whiteboard with an AI facilitator","description":"","apps":["anthropic"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"A Miro-like infinite canvas: sticky notes, shapes and text persisted per board,\nplus an AI step that re-arranges the board on request (\"group these by theme\",\n\"turn this into a timeline\").\n\n## Windmill concepts demonstrated\n\n- **Raw app** — the whiteboard canvas itself.\n- **AI script** — rearranges the board on request.\n- **Resource + variable** — the Anthropic key, and which model to use.\n- **Datatables** — stores boards and their elements.\n\n## Layout\n\n```\nmiro_list_boards.ts    All boards + element counts, most recently updated first\nmiro_create_board.ts   Empty board\nmiro_get_board.ts      Board + its elements\nmiro_rename_board.ts\nmiro_save_board.ts     Autosave: persist the full element set\nmiro_delete_board.ts   Board + elements\nmiro_ai_organize.ts    AI facilitator — request + elements → rearranged elements\nmiro_board__raw_app/   The canvas\n```\n\n## Try it locally\n\n```bash\nwmill script preview f/miro/miro_list_boards -d '{}'\n```","department":"marketing","items":[]},{"id":48,"slug":"support-automation","name":"Support automation","summary":"AI support triage with a human approval gate: classify, analyze, draft the reply, dispatch clear-cut fixes in parallel.","description":"","apps":["anthropic","github","sendgrid","slack"],"forks":0,"stars":0,"starred":false,"author":"hugo989","hasLogo":true,"readme":"# Support automation (minimal)\n\nA minimal version of the AI + human-in-the-loop support automation described in\n[Automating quality support at scale](https://www.windmill.dev/blog/support-automation):\na customer message, arriving from Slack or by email, is classified, triaged by\nAI, and answered on the channel it came from with a human-approved draft,\nwhile clear-cut fixes are dispatched in parallel.\n\n## How it works\n\n```\nSlack message ── HTTP trigger (slack_support_webhook) ──┐\nEmail ────────── email trigger (support_email) ─────────┤\n                                                        ▼\n  support_intake flow\n    ├─ acknowledge          (early return: acks Slack, echoes url_verification)\n    ├─ classify_message     (fast AI call: title, category, severity)\n    ├─ open_triage_thread   (thread in the team triage channel)\n    └─ trigger_post_processing  (async hand-off, never blocks new messages)\n         └─ triage_post_processing flow\n              ├─ analyze_ticket   (AI: root cause, draft reply, fix assessment)\n              ├─ post_triage      (analysis + draft into the triage thread)\n              └─ dispatch_parallel (branchall, parallel)\n                   ├─ request_approval → check_approval → send_reply\n                   │    (suspend: a human approves, edits or rejects; only then\n                   │     does the reply go back where the customer wrote from,\n                   │     Slack thread or email)\n                   └─ dispatch_fix\n                        (high-confidence clear-cut fixes become a labeled\n                         GitHub issue, independent of the reply approval)\n```\n\nTwo flows on purpose: intake is fast and always responsive, analysis is slow\nand runs async, so a long analysis never queues new incoming messages.\n\n## Try it without credentials\n\nEvery integration step has a demo mode: when its credential is empty (the\ndefault right after import), it logs what it would send and returns canned\ndata instead of calling out. So you can run either flow immediately - for\nexample `triage_post_processing` with just a `message` - watch every step go\ngreen, and approve the reply from the approval page URL printed in the\n`pause for human approval` step's logs. Fill in the resources below and the\nsame steps switch to doing the real thing.\n\n## Setup\n\n1. Fill in the resources in this folder:\n   - `slack_bot`: a Slack bot token (xoxb-...) with `chat:write` in the support\n     and triage channels and `channels:history` for events.\n   - `anthropic`: your Anthropic API key.\n   - `github`: a token with issues:write on the fix repository.\n   - `sendgrid` (email input only): a SendGrid API key for outbound replies.\n2. Set the variables:\n   - `triage_channel_id`: channel id of your internal triage channel.\n   - `fix_repo`: `org/repo` for auto-dispatched fix issues.\n   - `support_from_email` (email input only): verified sender address for\n     replies.\n3. In your Slack app, enable Event Subscriptions, subscribe to\n   `message.channels`, and set the request URL to this workspace's route for\n   `support_automation/slack_events`. The trigger answers the url_verification\n   handshake automatically.\n4. Invite the bot to both channels.\n5. Email input (optional): the `support_email` trigger reserves the `support`\n   local part, so mails to `<workspace>-support@<your windmill email domain>`\n   flow into the same intake. Forward your public support address there.\n   Approved replies are sent with SendGrid, threaded onto the customer's\n   message via In-Reply-To.\n\n## What the full version adds\n\nThis is the skeleton of the system in the blog post. The production version\nreplaces `analyze_ticket` with a sandboxed coding agent that has the codebase,\nthe docs and the customer's telemetry mounted (that is what makes the root\ncauses good), takes tickets from Discord too, hands clear-cut fixes\nto a coding agent that drafts the PR, pings the customer when the fix is\nmerged and released, escalates unanswered tickets, and feeds approval edits\nback into the drafting prompt.","items":[]},{"id":49,"slug":"recruit","name":"Recruit","summary":"Application form + AI CV screening","description":"","apps":["anthropic"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"Candidates apply through a public form with their CV; Windmill stores the PDF,\nextracts its text, and scores it against the job's criteria with Claude.\nRecruiters read the ranked score matrix in a dashboard.\n\n## Windmill concepts demonstrated\n\n- **Two raw apps** — a public application form, and an internal screening dashboard.\n- **Object storage** — keeps the uploaded CV PDFs.\n- **Background job** — screening runs after the applicant already got their confirmation.\n- **AI script** — scores the CV against the job's criteria.\n- **Resource + variable** — the Anthropic key, and which model to use.\n- **Datatables** — jobs and candidates.\n\n## Layout\n\n```\nrecruit_create_job.ts          Job opening + its screening criteria (a list of strings)\nrecruit_list_jobs.ts           Jobs + candidate / screened counts\nrecruit_get_job.ts             Public job detail (used by the apply form)\nrecruit_delete_job.ts          Job + candidates (cascade)\nrecruit_submit_application.ts  Decode PDF → S3 → extract text → create candidate → screen async\nrecruit_screen_candidate.ts    AI scoring against the job criteria\nrecruit_list_candidates.ts     Candidates ranked by overall score (score matrix)\nrecruit_get_candidate.ts       CV text, per-criterion scores, rationales\nrecruit_delete_candidate.ts    Candidate + stored CV object\nrecruit_apply__raw_app/        Public application form\nrecruit_dashboard__raw_app/    Screening dashboard\n```\n\n## Try it locally\n\n```bash\nwmill script preview f/recruit/recruit_list_jobs -d '{}'\n```","department":"hr","items":[]},{"id":87,"slug":"content-engine","name":"Content engine","summary":"Content engine","description":"","apps":["anthropic","discord","github"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":false,"readme":"# Content engine\n\nA Claude agent writes a piece of content, opens it as a GitHub pull request, and sends the draft to Discord. You merge what you like and close what you don't.\n\n\n## How it works\n\nEverything the writer knows lives in a GitHub repo — its skills say how to write, its folders hold everything already published. The agent reads that repo, writes, and opens a PR against it.\n\nMerging a PR keeps a source of truth of the content you post, and the agent reads it before writing the next piece — so the more you merge, the more it sounds like you. Nothing is published anywhere: the PR is the deliverable.\n\n## Setup\n\n**1. Add three resources in Windmill**\n\n- GitHub — a token that can create repos and write to them\n- Anthropic — your API key\n- Discord — the URL of a webhook, from a private channel\n\n**2. Run `init_content_repo`**\n\nCreates a dedicated GitHub repo with everything the writer needs. Set `dry_run: false` to actually create it.\n\n**3. Run the `content_run` flow**\n\nSet `dry_run: false` and go. You get a pull request and a Discord message. The first run takes a few minutes to warm up; the next ones are faster.\n\nTo make it daily, enable the `content_run` schedule — weekdays at 9am.\n\n## Making it yours\n\n**Edit the skills in the repo.** \n`strategy` decides what to write about, `voice` how it sounds, `format-<channel>` the shape per channel. This is where\nyou steer the writing.\n\n**Correct a draft before merging it.** \nThe agent reads past pieces to match the voice, so your edits become the examples it learns from.\n\n**Use Windmill AI sessions** \nTo change the engine itself — adding a channel, changing what the agent is told, adjusting the flow. Describe what you want and\niterate from there.","department":"marketing","items":[]},{"id":88,"slug":"support-engine","name":"Support engine","summary":"Drafts replies to your support inbox into Gmail drafts, ready for you to send.","description":"","apps":["anthropic","discord","github","gmail"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":false,"readme":"# Support engine\n\nA Claude agent reads your support inbox, drafts a reply to each new message, and saves it to Gmail drafts. Once a day it tells you on Discord how many are waiting.\n\nNothing is ever sent. You open the draft, read it, and press send.\n\n## How it works\n\nEverything the agent knows lives in a GitHub repo — its skills say when to answer, how a reply sounds, and where facts may come from. The `answers/` folder holds replies that were actually sent, and the agent reads the closest ones before writing.\n\nSo the more good replies you keep in `answers/`, the more the next one sounds like your team.\n\nIt doesn't answer everything. Newsletters, billing, legal, angry senders, anything it would have to guess at — it escalates instead of drafting, and tells you why. A support inbox is not all support requests.\n\n## The pieces\n\n- **`init_support_repo`** — one-shot, creates the GitHub repo with a working set of skills.\n- **`support_run`** — every 30 minutes on weekdays: read the inbox, draft, save to Gmail drafts.\n- **`daily_digest`** — 5pm: one Discord message with how many replies are waiting.\n\n## Setup\n\n**1. Add four resources in Windmill**\n\n- GitHub — a token that can create repos and read them\n- Anthropic — your API key\n- Gmail — connect the account, with the `gmail.modify` scope\n- Discord — the URL of a webhook, from a private channel\n\nThe Gmail scope matters: `gmail.modify` covers reading messages, creating drafts and labelling. `gmail.readonly` won't be enough.\n\n**2. Run `init_support_repo`**\n\nCreates a dedicated GitHub repo with everything the agent needs. Set `dry_run: false` to actually create it.\n\n**3. Run the `support_run` flow**\n\nSet `dry_run: false` and go. You get a draft in Gmail, in the original thread. The first run takes a few minutes to warm up; the next ones are faster.\n\nTo make it continuous, enable the `support_run` and `daily_digest` schedules.\n\n## Making it yours\n\n**Edit the skills in the repo.** `triage` decides what to answer at all, `voice` how it sounds, `format-email` the shape, `knowledge` where facts come from. Point `knowledge` at your documentation — it's the edit that most improves the replies.\n\n**Keep good replies.** When one turns out well, save it in `answers/` as `<date>-<slug>.md`. That folder is read before every draft, and it's what makes support consistent.\n\n**Watch what gets escalated.** If the agent hands you things it should handle, the answer is usually missing from the repo, not from the skills.\n\n**Use Windmill AI sessions** to change the engine itself — how often it checks, what the digest says, what the agent is told. Describe what you want and iterate from there.","items":[]},{"id":89,"slug":"sales-engine","name":"Sales engine","summary":"Finds leads matching your ICP, drafts an email to the ones worth writing to, and leaves them in Gmail drafts.","description":"","apps":["anthropic","apollo","github","gmail"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":false,"readme":"# Sales engine\n\nFinds leads matching your ICP, researches each one, writes an email to the ones that deserve it, and leaves them in Gmail drafts. You open a draft and press send.\n\nNothing is ever sent without you.\n\n## How it works\n\nEverything the engine knows lives in a GitHub repo. `config/icp.json` holds the Apollo filters — that is the targeting, versioned, so changing who you contact is a pull request. The skills say what makes a lead worth an email, what counts as a reason to write, and how outreach sounds.\n\nApollo answers \"who matches the filters\". It cannot tell you whether there is a reason to write, and that is the agent's job: it looks for a concrete, checkable fact about that company — a job posting, something they shipped — and **drops the lead if it cannot find one**. Expect it to drop several. A generic email costs the address permanently.\n\nEvery run opens a pull request with two folders: `sent/` for the emails written, `dropped/` for the companies it passed on and why. Merging it keeps the record the next run reads — so the engine stops re-researching the same company every week, and stops writing twice to the same person.\n\n## Setup\n\n**1. Add four resources in Windmill**\n\n- GitHub — a token that can create repos and write to them\n- Anthropic — your API key\n- Apollo — your API key\n- Gmail — the account the drafts are written in\n\n**2. Run `init_sales_repo`**\n\nCreates the repo with the skills and a starting ICP. Set `dry_run: false` to actually create it.\n\n**3. Check `config/icp.json`**\n\nTitles, countries, company sizes. This decides who gets contacted, so read it once before the first real run.\n\n**4. Run the `sales_run` flow**\n\nSet `dry_run: false` and go. You get drafts in Gmail and a pull request. Count on a few minutes: the agent researches each company before writing.\n\nTo make it weekly, enable the `sales_run` schedule — Monday morning.\n\n## Making it yours\n\n**Edit the ICP** when the leads are wrong. Edit the **skills** when the emails are wrong: `icp` for who is worth writing to, `research` for what counts as a reason, `voice` and `format-email` for how it reads.\n\n**Read the drops, not just the emails.** They are in the pull request with their reasons, and they tell you more about your targeting than the emails do. A run that drops everything usually means the ICP is too broad.\n\n**Merge the PR once you have sent the drafts.** That is what keeps the memory: the next run reads it and leaves those companies alone.\n\n**Use Windmill AI sessions** to change the engine itself — how many leads per run, what the agent is told, what gets recorded. Describe what you want and iterate from there.","department":"sales","items":[]},{"id":90,"slug":"sentry-engine","name":"Sentry engine","summary":"Investigates recurring production errors and opens a pull request with the diagnosis, and the fix when it is sure.","description":"","apps":["anthropic","discord","github","sentry"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":false,"readme":"# Sentry engine\n\n**Investigates recurring production errors and opens a pull request with the diagnosis, and the fix when it is sure.**\n\nLooks at production errors that keep happening, works out what is actually wrong, and opens a pull request with the analysis — plus the fix, when it is sure of it. Discord gets a message either way.\n\nNothing lands on your default branch without you merging it.\n\n## How it works\n\nThere is nothing to configure beyond pointing it at a repo: the one it reads **is your codebase**. It clones it, opens the stack trace next to the code, and follows the data backwards.\n\n**The analysis is the deliverable. The fix is optional.** A stack trace tells you where the program stopped, not why it was wrong — most production errors are symptoms of something upstream, and for those the obvious patch is a guard at the crash site. The error leaves Sentry, the bug stays, and it goes quiet. So the agent is allowed to change nothing and say why, and the pull request leads with the reasoning rather than the diff.\n\nThat is also where the time actually goes. Fixing a Sentry error is rarely the five minutes of writing the patch — it is the twenty minutes of reading the trace, finding the code, and working out what input produced it. That part it does well.\n\nEvery investigation writes `.sentry/investigated/<issue>.md` into the same PR. Merged, it becomes the record: the engine never looks at that issue twice, and the next run reads what you already concluded.\n\n## Setup\n\n**1. Add four resources in Windmill**\n\n- Sentry — an auth token with `event:read` and `project:read`\n- GitHub — a token that can write Contents and Pull requests on the repo\n- Anthropic — your API key\n- Discord — the URL of a webhook\n\n**2. Set `repo`, `org` and `project` on the flow**\n\n`org` and `project` are the slugs from your Sentry URL. `repo` is the codebase those errors come from.\n\n**3. Run `sentry_run`**\n\nSet `dry_run: false` and go. You get a pull request per issue and a Discord message per issue.\n\n**4. Enable the schedule** — three times a day on weekdays.\n\n## One codebase per schedule\n\nA Sentry project maps to one repo, so a backend and a frontend are two schedules on the **same flow**, not two flows:\n\n```\nsentry_run_backend.schedule.yaml    project: ifc-api   repo: The-IFC/IFC-Api\nsentry_run_frontend.schedule.yaml   project: ifc-app   repo: The-IFC/IFC-App\n```\n\nOnly the arguments differ. Add a third service and it is a six-line file, not a third pipeline.\n\n## Making it yours\n\n**Tune `min_events` on real numbers.** The threshold is measured over the window, not over the issue's lifetime. Run `poll_sentry` alone with `min_events: 1` and look at the counts it returns — a quiet service may see single digits over 14 days, a busy one hundreds in an hour. Set it just above your noise floor. Too high and nothing ever fires; too low and it investigates one-offs.\n\n**Read the ones with no fix.** They are the majority and they are not failures — they are the investigation you did not have to do. A third party returning 503 has no fix in your code, and saying so clearly is the right output.\n\n**Merge to remember, close to forget.** Merging keeps the analysis and stops the engine revisiting that issue. Closing it means the issue can come back — which is what you want when the analysis was wrong.\n\n**Use Windmill AI sessions** to change the engine itself — what the agent is told, what goes in the record, how often it runs.","department":"engineering","items":[]},{"id":136,"slug":"revops-lead-routing","name":"RevOps lead routing","summary":"Import, dedupe, AI-score and route inbound leads every hour, then track time to first touch per rep","description":"","apps":["anthropic","hubspot"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"# Watch it in one minute\n\n[![Route every new lead with Windmill](https://img.youtube.com/vi/ihBbn_LOqC8/maxresdefault.jpg)](https://www.youtube.com/watch?v=ihBbn_LOqC8)\n\n# What this workspace does\n\nRoutes every new inbound lead to the right rep within the hour.\n\n- **Data pipeline**: every hour, `import_leads` imports the new leads from the website form, CSV imports and event lists, then `merge_and_normalize_leads` merges duplicates (same email, or same company domain and name) and normalizes source, company, role and country.\n- **AI agent**: `score_lead` researches the company with two script tools (company research, CRM account lookup), scores the lead against your ideal customer profile and returns a summary, a score and a one-line reason.\n- **Flow**: `lead_routing` runs for each new lead. Above the threshold, it picks the rep by territory and current load, creates the lead in the CRM and notifies the rep.\n- **Schedule**: every hour, `follow_up_unpicked_leads` reminds the rep of a lead not picked up within 24 hours, then reassigns it.\n- **App**: `lead_dashboard` lists today's leads with their score, reason, owner and first touch, and the average time to first touch per rep.\n\n# Who it's for\n\nRevOps teams and sales managers who clean, enrich and assign leads by hand in a spreadsheet.\n\n# How to use it\n\n1. Run `seed_demo_data` to load fictional demo data: reps and their territories, routing rules, company profiles, CRM accounts and 85 incoming leads.\n2. Fill the `anthropic` resource used by `score_lead`.\n3. Run `import_leads`, or turn on its schedule: the pipeline dedupes the leads and starts `lead_routing` for each one.\n4. Open `lead_dashboard`.\n\nTo go live, replace the demo tables with your own sources, pass a HubSpot resource to the CRM steps and a Slack webhook to `notify_rep`.","items":[]},{"id":137,"slug":"month-end-close","name":"Month-end close","summary":"Match bank transactions to invoices every night, route each discrepancy for approval and let an AI agent explain it","description":"","apps":["anthropic","smtp"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"# Watch it in one minute\n\n[![Close the month faster with Windmill](https://img.youtube.com/vi/lzw9l9FTDHA/maxresdefault.jpg)](https://www.youtube.com/watch?v=lzw9l9FTDHA)\n\n# What this workspace does\n\nShows where the month-end close stands, and clears the discrepancies faster.\n\n- **Data pipeline**: every night, `import_bank_transactions` imports the bank's CSV export and the accounting system's invoice export, then `match_transactions` matches each transaction to an invoice (same amount, due date within 10 days, invoice number in the reference). Every line it cannot match becomes a discrepancy, with its type and owner.\n- **Flow**: `discrepancy_approval` runs for each new discrepancy. It asks the owner to approve or reject it in Slack or by email, sends a reminder after 2 days without an answer, then escalates to the controller.\n- **AI agent**: `explain_discrepancy` reads a discrepancy, looks at the bank line and searches the invoices with two script tools, then gives the likely cause in two sentences (duplicate invoice, partial payment, bank fee...), proposes the correcting entry without posting it, and drafts an email to the supplier when needed.\n- **App**: `close_dashboard` shows the close progress, the open discrepancies by owner and the pending approvals. An Explain button runs the agent on a line.\n\n# Who it's for\n\nFinance teams and controllers who close the month in spreadsheets and chase discrepancies by email.\n\n# How to use it\n\n1. Run `seed_demo_data` to load a month of fictional demo data: suppliers, cost centers, 1,290 invoices and 1,284 bank transactions, including a duplicate invoice.\n2. Fill the `anthropic` resource used by `explain_discrepancy`.\n3. Open `close_dashboard` and click Explain on a discrepancy.\n4. To replay the night, run `seed_demo_data` with `stage` set to `before_night`, then run `import_bank_transactions`.\n\nTo go live, pass the URLs of your bank and invoice exports to `import_bank_transactions`, and a Slack webhook or an SMTP resource to the approval steps.","items":[]},{"id":138,"slug":"support-ticket-triage","name":"Support ticket triage","summary":"Classify each ticket with an AI decision, draft the reply with an AI agent, approve large refunds and track the queue in an app","description":"","apps":["anthropic","smtp","typesafe"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"# Watch it in one minute\n\n[![Triage support tickets with Windmill](https://img.youtube.com/vi/C8g1KJGfKPo/maxresdefault.jpg)](https://www.youtube.com/watch?v=C8g1KJGfKPo)\n\n# What this workspace does\n\nSorts every new support ticket, drafts the reply, and keeps urgent tickets from getting lost in the queue.\n\n- **AI decision**: `ticket_triage` runs for each new ticket, from a webhook or an email trigger. An AI decision step classifies it (bug, billing or question) and rates its urgency (can wait, this week, today, right now), then a branch routes it to the right queue.\n- **AI agent**: in the same flow, an AI agent step looks up the customer and their orders with two script tools and drafts a reply. Nothing is sent: the draft waits for a person to review it.\n- **Flow**: `refund` records a refund on an order. Under 200 EUR it goes through on its own; from 200 EUR a manager has to approve it. Then the order is updated and the customer gets a reply.\n- **Data pipeline**: every night, `daily_ticket_stats` writes the day's tickets per category, urgent tickets, first response time and refunds.\n- **App**: `support_desk` shows the queue sorted by urgency, lets an agent review and send each draft, approve pending refunds, and see yesterday's response times per category.\n\n# Who it's for\n\nSupport teams who triage a shared inbox by hand and answer the same questions every day.\n\n# How to use it\n\n1. Run `seed_demo_data` to load fictional demo data: customers, orders, a week of tickets and a refund waiting for a manager.\n2. Fill the `typesafe` resource used by the AI decision step and the `anthropic` resource used by the AI agent step.\n3. Run `ticket_triage` with a customer email, a subject and a body, or turn on its webhook or email trigger.\n4. Open `support_desk`.\n\nTo go live, point your support tool's webhook or your support inbox at the triggers, and pass an SMTP resource to `send_reply` and a Slack webhook to the refund approval step.","items":[]}]}