{"projects":[{"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":[]},{"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":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":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":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 in Slack is classified, triaged by AI, and answered with a\nhuman-approved draft, while 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":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":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":[]}]}