{"projects":[{"id":52,"slug":"odoo","name":"Odoo","summary":"Manages Odoo records interactively.","description":"","apps":["misc"],"forks":0,"stars":2,"starred":false,"author":"drpsyko101653","hasLogo":true,"readme":"## Description\n\nManages Odoo records interactively from these simple-to-use nodes.\n\n## Usage\n\n1. Generate an API key from the Odoo security tab within the user settings.\n2. Create a `odoo_api` resource with the fully qualified domain name of the Odoo instance, username, the generated API key and the database name.","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":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.","items":[]},{"id":10,"slug":"lemlist","name":"Lemlist","summary":"Cold-email sequences with a real sending engine","description":"","apps":["smtp"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"Audiences, multi-step campaigns, a\nscheduled sending engine, open tracking, and stop-on-reply. A lemlist clone\nwhere every moving part is an inspectable Windmill job.\n\n## Windmill concepts demonstrated\n\n- **Two raw apps** — one for campaigns and sequences, one for contacts and audiences.\n- **Schedule** — runs the sending engine every ten minutes.\n- **Flow with a parallel loop** — sends each due email as its own retryable job.\n- **HTTP route** — serves the open-tracking pixel.\n- **Email trigger** — a reply stops that contact's sequence.\n- **Resource + variables** — the SMTP account and the support address.\n- **Datatables** — contacts, audiences, campaigns, steps, enrollments, events.\n\n## Domain model\n\n- **Contact** — global pool, keyed by email.\n- **Audience** (`list`) — a named set of contacts; deleting one cascades membership, not contacts.\n- **Campaign** — an ordered sequence of steps (subject, body, delay), status `draft` / `active` / `paused`.\n- **Enrollment** — one contact's progress through one campaign. Advances step by step, stops on reply or unsubscribe.\n- **Event** — sends and opens.\n\n## Layout\n\n```\nAudiences        lemlist_create_list / get_list / list_lists / delete_list\n                 lemlist_add_contacts / add_to_list / add_existing_to_list / remove_member\nContacts         lemlist_list_contacts\nCampaigns        lemlist_create_campaign / get_campaign / list_campaigns / delete_campaign\n                 lemlist_save_steps / lemlist_set_status\nEnrollment       lemlist_enroll / lemlist_enroll_list / lemlist_stop_enrollment\nEngine           lemlist_query_due → lemlist_send_one   (flow)\n                 lemlist_process_due                     (single script, scheduled)\nTracking         lemlist_track_open  + lemlist_track_open.http_trigger.yaml\nReplies          lemlist_inbound_reply + lemlist_inbound_reply.email_trigger.yaml\nApps             lemlist_dashboard__raw_app/ · lemlist_audiences__raw_app/\n```\n\n## Going live\n\n1. Fill in `smtp.resource.yaml` with a real SMTP account.\n2. Set the `f/lemlist/support_address` variable.\n3. Enable `lemlist_engine.schedule.yaml` (or point the schedule at\n   `lemlist_send_flow` for the fan-out engine).\n\n## Try it locally\n\n```bash\nwmill script preview f/lemlist/lemlist_query_due -d '{}'\nwmill flow preview f/lemlist/lemlist_send_flow -d '{\"smtp\":\"$res:f/lemlist/smtp\"}'\n```","items":[]},{"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```","items":[]},{"id":6,"slug":"bitly","name":"Bitly","summary":"Short link shortener with click analytics","description":"","apps":[],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"A self-hosted Bitly clone: create short links, serve the redirect, record every\nclick, and browse the analytics from a dashboard app.\n\n## Windmill concepts demonstrated\n\n- **Raw app** — the dashboard: create, list, inspect and delete links.\n- **HTTP route trigger** — serves the public redirect at `l/:slug`.\n- **Preprocessor** — turns the raw HTTP request into the handler's arguments.\n- **Custom HTTP response** — returns a real 302 redirect instead of JSON.\n- **Datatables** — stores the links and every click.\n\n## Layout\n\n```\nbitly_create_link.ts        Create a short link (slug optional → generated)\nbitly_list_links.ts         All links + total clicks, last click, status\nbitly_get_link_detail.ts    One link + 30-day click series (analytics)\nbitly_delete_link.ts        Delete a link, clicks cascade\nbitly_redirect.ts           Route handler: resolve slug, log click, 302\nbitly_redirect.http_trigger.yaml\nbitly_dashboard__raw_app/   Dashboard UI (create / list / inspect / delete)\n```\n\n## How it runs\n\n1. A visitor hits `<instance>/api/r/w/<workspace>/l/<slug>`.\n2. The preprocessor pulls `slug`, `referrer`, `user_agent` off the request.\n3. `main` looks up the link in `bitly.links`, inserts a row into `bitly.clicks`\n   (best-effort — a failed insert never blocks the redirect), and returns the 302.\n4. The dashboard reads the aggregates back through the CRUD scripts.\n\n## Try it locally\n\n```bash\nwmill script preview f/bitly/bitly_list_links -d '{}'\nwmill app dev            # from inside bitly_dashboard__raw_app/\n```","items":[]},{"id":12,"slug":"typeform","name":"Typeform","summary":"Form builder, public form, responses dashboard","description":"","apps":["misc"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"A Typeform clone in three apps: build a form, let anyone fill it, read the\nresponses.\n\n## Windmill concepts demonstrated\n\n- **Three raw apps** — build a form, fill it in, read the responses.\n- **Datatables** — forms, questions and responses.\n- **Per-app backends** — the public form only reaches the two scripts it needs.\n\n## Layout\n\n```\ntypeform_list_forms.ts        Forms + question / response counts, newest first\ntypeform_save_form.ts         Upsert a form and replace its questions\ntypeform_get_form_config.ts   Form + ordered questions (one config object)\ntypeform_delete_form.ts       Form + questions + responses\ntypeform_submit.ts            Persist a submission\ntypeform_get_responses.ts     Responses, newest first (default limit 100)\ntypeform_builder__raw_app/    Authoring\ntypeform_form__raw_app/       Public form\ntypeform_dashboard__raw_app/  Responses\n```\n\n## Try it locally\n\n```bash\nwmill script preview f/typeform/typeform_list_forms -d '{}'\n```","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":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.","items":[]},{"id":50,"slug":"github-release-digest","name":"Github release digest","summary":"Browse a repo's latest releases","description":"","apps":["github","slack"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":false,"readme":"Fetch the latest GitHub releases of any repository and read them either as a\nformatted digest (flow) or in a dashboard (raw app).\n\n## Windmill concepts demonstrated\n\n- **Flow** — fetch the releases, then format them into a digest.\n- **Inline flow step** — the formatting glue lives in the flow, not in a shared script.\n- **Resource** — optional GitHub token, for private repos and rate limits.\n- **Raw app** — browse the releases in a dashboard.\n- **`dry_run`** — preview the digest before any delivery.\n\n## Layout\n\n```\nfetch_releases.ts                     Paginated GitHub releases fetch (optional auth)\nrelease_digest__flow/\n  flow.yaml                           fetch → format\n  format_a_readable_digest.ts         Inline step: Markdown digest, body truncation\ndashboard__raw_app/                   Dashboard UI (own inline backend)\n```\n\n## Related\n\n`f/github_release_digest` is the delivery-oriented sibling: same fetch, but the\ndigest is posted to Slack by a third flow step.\n\n## Try it locally\n\n```bash\nwmill flow preview f/github_release_dashboard/release_digest \\\n  -d '{\"owner\":\"windmill-labs\",\"repo\":\"windmill\",\"count\":5,\"dry_run\":true}'\n```","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```","items":[]},{"id":14,"slug":"uptimerobot","name":"Uptimerobot","summary":"Uptime monitoring with self-managed schedules","description":"","apps":["smtp"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"Register a URL, and Windmill pings it on a cron of your choosing, records every\ncheck, and alerts by email or Slack — but only when the up/down state actually\nchanges.\n\n## Windmill concepts demonstrated\n\n- **Raw app** — add monitors and read their status.\n- **Schedules created from a script** — each monitor becomes a real Windmill cron.\n- **Schedule args** — a monitor's config lives on its schedule.\n- **Resource** — SMTP for the alert emails.\n- **Datatables** — monitors and their check history.\n\n## Layout\n\n```\nuptime_create_monitor.ts      Insert monitor + create its Windmill schedule\nuptime_get_monitors.ts        Monitors + current status\nuptime_get_monitor_detail.ts  One monitor + its check history\nuptime_delete_monitor.ts      Monitor + its schedule\nuptime_check.ts               The probe — run by every monitor's cron\nmonitor_<uuid>.schedule.yaml  A generated monitor schedule (ships disabled)\nuptime_dashboard__raw_app/    Monitor dashboard\n```\n\n`uptime_check` must be **deployed** for schedules to invoke it — a schedule\npoints at a workspace path, not at local files.\n\n## Try it locally\n\n```bash\nwmill script preview f/uptimerobot/uptime_check \\\n  -d '{\"monitor_id\":\"<uuid>\",\"url\":\"https://app.windmill.dev\"}'\n```","items":[]},{"id":13,"slug":"helpdesk","name":"Helpdesk","summary":"Email-in support inbox","description":"","apps":["smtp"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"A minimal Zendesk: inbound email becomes a ticket, replies thread onto it, and\nagents work the queue from a dashboard app.\n\n## Windmill concepts demonstrated\n\n- **Email trigger** — mail sent to the support address opens or updates a ticket.\n- **Preprocessor** — normalizes messy email headers before the handler runs.\n- **Raw app** — the agent inbox: read threads, reply, change status.\n- **Resource + variables** — the SMTP account, and the address replies come from.\n- **Datatables** — stores tickets and their message threads.\n\n## Layout\n\n```\nhelpdesk_inbound.ts               Email-trigger handler (preprocessor + upsert ticket)\nhelpdesk_inbound.email_trigger.yaml\nhelpdesk_list_tickets.ts          Queue, optional status filter, most recent first\nhelpdesk_get_ticket.ts            One ticket + its full message thread\nhelpdesk_reply.ts                 Agent reply over SMTP, appended to the thread\nhelpdesk_update_status.ts         open / pending / closed\nhelpdesk_set_archived.ts          Archive toggle (archived tickets leave the queue)\nhelpdesk_dashboard__raw_app/      Support inbox UI\n```\n\n## How a ticket is born\n\n1. A customer emails `support@<workspace>...`.\n2. The email trigger fires `helpdesk_inbound`; the preprocessor normalizes the message.\n3. `main` finds an existing thread (or opens a new ticket) and appends the message.\n4. The agent replies from the dashboard → `helpdesk_reply` sends over SMTP and\n   records the outbound message on the same thread.\n\n## Try it locally\n\n```bash\nwmill script preview f/helpdesk/helpdesk_list_tickets -d '{\"status\":\"open\"}'\n```","items":[]},{"id":8,"slug":"calendly","name":"Calendly","summary":"Booking page + availability engine","description":"","apps":["gcal","smtp"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":true,"readme":"A Calendly clone: an admin configures availability once, guests pick a free slot\non a public booking page, and Windmill creates the Google Calendar event (with a\nMeet link) and sends the confirmation email.\n\n## Windmill concepts demonstrated\n\n- **Two raw apps** — a public booking page for guests, an admin page for availability.\n- **Custom resource type** — holds the availability config: hours, days, duration, timezone.\n- **Resources** — Google Calendar and SMTP credentials.\n- **Datatables** — stores the config and the bookings.\n- **Setup script** — creates the schema on first run, safe to re-run.\n\n## Layout\n\n```\ncalendly_setup_db.ts             Create schema + config/bookings tables (idempotent)\ncalendly_get_config.ts           Read the availability config\ncalendly_update_config.ts        Update host, title, hours, duration, timezone\ncalendly_get_available_slots.ts  Free slots for a date (config − existing bookings)\ncalendly_book_slot.ts            Create the Google Calendar event + Meet link, store\n                                 the booking, send the confirmation over SMTP\ncalendly_booking__raw_app/       Public booking page\ncalendly_dashboard__raw_app/     Admin configuration page\ncalendly__raw_app/               Leftover scaffold (no raw_app.yaml) — safe to delete\n```\n\n## Notable implementation details\n\n- Timezone handling is explicit: `toRfc3339WithOffset()` converts a naive\n  `YYYY-MM-DD` + `HH:MM` into an RFC3339 timestamp carrying the config's IANA\n  offset, so Google Calendar receives an unambiguous instant.\n- The Meet link is requested through `conferenceDataVersion=1` and\n  `sendUpdates=all` on the Calendar API call.\n- Scripts and descriptions in this project are written in French.\n\n## Try it locally\n\n```bash\nwmill script preview f/calendly/calendly_get_available_slots -d '{\"date_str\":\"2026-08-03\"}'\n```","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.","items":[]},{"id":11,"slug":"github-release-dashboard","name":"Github release dashboard","summary":"Browse a repo's latest releases","description":"","apps":["github"],"forks":0,"stars":0,"starred":false,"author":"tristan795","hasLogo":false,"readme":"Fetch the latest GitHub releases of any repository and read them either as a\nformatted digest (flow) or in a dashboard (raw app).\n\n## Windmill concepts demonstrated\n\n- **Flow** — fetch the releases, then format them into a digest.\n- **Inline flow step** — the formatting glue lives in the flow, not in a shared script.\n- **Resource** — optional GitHub token, for private repos and rate limits.\n- **Raw app** — browse the releases in a dashboard.\n- **`dry_run`** — preview the digest before any delivery.\n\n## Layout\n\n```\nfetch_releases.ts                     Paginated GitHub releases fetch (optional auth)\nrelease_digest__flow/\n  flow.yaml                           fetch → format\n  format_a_readable_digest.ts         Inline step: Markdown digest, body truncation\ndashboard__raw_app/                   Dashboard UI (own inline backend)\n```\n\n## Related\n\n`f/github_release_digest` is the delivery-oriented sibling: same fetch, but the\ndigest is posted to Slack by a third flow step.\n\n## Try it locally\n\n```bash\nwmill flow preview f/github_release_dashboard/release_digest \\\n  -d '{\"owner\":\"windmill-labs\",\"repo\":\"windmill\",\"count\":5,\"dry_run\":true}'\n```","items":[]}]}