From e1e3e634fa083d26059e71066d6c47d71cc0c46a Mon Sep 17 00:00:00 2001 From: Ben McAdams Date: Mon, 20 Jul 2026 15:07:18 -0700 Subject: [PATCH] Add hospitality, e-learning, and non-profit domain examples Grounded in Zapier's own vertical-usage data: three new by-pattern workflows for the flagship intake shapes each vertical actually runs (Facebook Lead Ads/Typeform/Gravity Forms/Eventbrite into Google Sheets, plus a sheet-triggered Gmail notify), symlinked into new by-domain/hospitality, by-domain/e-learning, and by-domain/non-profit directories alongside existing workflows they share. --- examples/by-domain/e-learning/README.md | 33 +++++++++ .../facebook-lead-ads-to-google-sheets | 1 + .../e-learning/facebook-lead-ads-to-hubspot | 1 + .../e-learning/typeform-to-google-sheets | 1 + examples/by-domain/hospitality/README.md | 33 +++++++++ .../facebook-lead-ads-to-google-sheets | 1 + .../hospitality/google-sheets-row-to-gmail | 1 + .../hospitality/typeform-submission-to-gmail | 1 + examples/by-domain/non-profit/README.md | 33 +++++++++ .../non-profit/eventbrite-to-google-sheets | 1 + .../non-profit/gravity-forms-to-google-sheets | 1 + .../non-profit/typeform-submission-to-gmail | 1 + .../eventbrite-to-google-sheets/README.md | 33 +++++++++ .../eventbrite-to-google-sheets/package.json | 12 ++++ .../eventbrite-to-google-sheets/workflow.ts | 50 +++++++++++++ .../README.md | 34 +++++++++ .../package.json | 12 ++++ .../workflow.ts | 71 +++++++++++++++++++ .../gravity-forms-to-google-sheets/README.md | 33 +++++++++ .../package.json | 12 ++++ .../workflow.ts | 50 +++++++++++++ .../typeform-to-google-sheets/README.md | 33 +++++++++ .../typeform-to-google-sheets/package.json | 12 ++++ .../typeform-to-google-sheets/workflow.ts | 52 ++++++++++++++ .../google-sheets-row-to-gmail/README.md | 30 ++++++++ .../google-sheets-row-to-gmail/package.json | 12 ++++ .../google-sheets-row-to-gmail/workflow.ts | 42 +++++++++++ 27 files changed, 596 insertions(+) create mode 100644 examples/by-domain/e-learning/README.md create mode 120000 examples/by-domain/e-learning/facebook-lead-ads-to-google-sheets create mode 120000 examples/by-domain/e-learning/facebook-lead-ads-to-hubspot create mode 120000 examples/by-domain/e-learning/typeform-to-google-sheets create mode 100644 examples/by-domain/hospitality/README.md create mode 120000 examples/by-domain/hospitality/facebook-lead-ads-to-google-sheets create mode 120000 examples/by-domain/hospitality/google-sheets-row-to-gmail create mode 120000 examples/by-domain/hospitality/typeform-submission-to-gmail create mode 100644 examples/by-domain/non-profit/README.md create mode 120000 examples/by-domain/non-profit/eventbrite-to-google-sheets create mode 120000 examples/by-domain/non-profit/gravity-forms-to-google-sheets create mode 120000 examples/by-domain/non-profit/typeform-submission-to-gmail create mode 100644 examples/by-pattern/lead-routing/eventbrite-to-google-sheets/README.md create mode 100644 examples/by-pattern/lead-routing/eventbrite-to-google-sheets/package.json create mode 100644 examples/by-pattern/lead-routing/eventbrite-to-google-sheets/workflow.ts create mode 100644 examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/README.md create mode 100644 examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/package.json create mode 100644 examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/workflow.ts create mode 100644 examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/README.md create mode 100644 examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/package.json create mode 100644 examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/workflow.ts create mode 100644 examples/by-pattern/lead-routing/typeform-to-google-sheets/README.md create mode 100644 examples/by-pattern/lead-routing/typeform-to-google-sheets/package.json create mode 100644 examples/by-pattern/lead-routing/typeform-to-google-sheets/workflow.ts create mode 100644 examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/README.md create mode 100644 examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/package.json create mode 100644 examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/workflow.ts diff --git a/examples/by-domain/e-learning/README.md b/examples/by-domain/e-learning/README.md new file mode 100644 index 0000000..93788fc --- /dev/null +++ b/examples/by-domain/e-learning/README.md @@ -0,0 +1,33 @@ +# E-Learning + +Automations for online course providers and training platforms: enrollment-lead intake, CRM handoff, form-based signups. + +## Recurring app stack + +**Facebook Lead Ads, Typeform, Google Sheets, HubSpot.** E-learning marketers run heavy paid-acquisition funnels (course-interest ads, webinar signups) that need both a lightweight log and a CRM handoff for the sales/enrollment team. + +## Canonical workflows + +Each entry is a filesystem symlink to the canonical directory under `by-pattern/`. Follow it to read the workflow source and README. + +| Shape | Workflow | Apps | +|---|---|---| +| Flagship intake | [`facebook-lead-ads-to-google-sheets/`](./facebook-lead-ads-to-google-sheets) | Facebook Lead Ads -> Google Sheets | +| CRM routing | [`facebook-lead-ads-to-hubspot/`](./facebook-lead-ads-to-hubspot) | Facebook Lead Ads -> HubSpot | +| Trigger swap | [`typeform-to-google-sheets/`](./typeform-to-google-sheets) | Typeform -> Google Sheets | + +## Where e-learning teams typically wire these + +- **Flagship intake**: Meta lead-ads webhook (course-interest, webinar, free-trial forms). Marketing keeps a running log independent of the CRM. +- **CRM routing**: the same lead, upserted into HubSpot for the enrollment/sales team to work — HubSpot ranks in e-learning's top-5 apps. +- **Trigger swap**: quiz results, application forms, or webinar-registration Typeforms feed the same sheet-log destination as the Meta path. + +## Patterns not yet covered + +Common shapes an e-learning team would want that don't have workflows yet: + +- **Course-completion event -> certificate email**: platform webhook triggers a Gmail send with a completion certificate attached. +- **Google Sheets waitlist -> Gmail cohort-start reminder**: scheduled digest to everyone on a cohort's waitlist sheet. +- **Webinar-attendee (chat-triggered) -> Google Sheets log**: a chat-message trigger logging attendance for follow-up, per the AI-agent-adjacent pattern seen in e-learning's usage data. + +PRs welcome. diff --git a/examples/by-domain/e-learning/facebook-lead-ads-to-google-sheets b/examples/by-domain/e-learning/facebook-lead-ads-to-google-sheets new file mode 120000 index 0000000..ad214ed --- /dev/null +++ b/examples/by-domain/e-learning/facebook-lead-ads-to-google-sheets @@ -0,0 +1 @@ +../../by-pattern/lead-routing/facebook-lead-ads-to-google-sheets \ No newline at end of file diff --git a/examples/by-domain/e-learning/facebook-lead-ads-to-hubspot b/examples/by-domain/e-learning/facebook-lead-ads-to-hubspot new file mode 120000 index 0000000..b1c4b75 --- /dev/null +++ b/examples/by-domain/e-learning/facebook-lead-ads-to-hubspot @@ -0,0 +1 @@ +../../by-pattern/lead-routing/facebook-lead-ads-to-hubspot \ No newline at end of file diff --git a/examples/by-domain/e-learning/typeform-to-google-sheets b/examples/by-domain/e-learning/typeform-to-google-sheets new file mode 120000 index 0000000..a913bf3 --- /dev/null +++ b/examples/by-domain/e-learning/typeform-to-google-sheets @@ -0,0 +1 @@ +../../by-pattern/lead-routing/typeform-to-google-sheets \ No newline at end of file diff --git a/examples/by-domain/hospitality/README.md b/examples/by-domain/hospitality/README.md new file mode 100644 index 0000000..81c8a4f --- /dev/null +++ b/examples/by-domain/hospitality/README.md @@ -0,0 +1,33 @@ +# Hospitality + +Automations for hotels, restaurants, and event venues: lead-capture ingest, sheet-native intake, guest follow-up. + +## Recurring app stack + +**Facebook Lead Ads, Typeform, Google Sheets, Gmail.** Hospitality accounts run lighter-weight ops than an enterprise CRM shop — a spreadsheet is often the CRM, and Gmail is often the notification layer. + +## Canonical workflows + +Each entry is a filesystem symlink to the canonical directory under `by-pattern/`. Follow it to read the workflow source and README. + +| Shape | Workflow | Apps | +|---|---|---| +| Flagship intake | [`facebook-lead-ads-to-google-sheets/`](./facebook-lead-ads-to-google-sheets) | Facebook Lead Ads -> Google Sheets | +| Sheet-as-trigger | [`google-sheets-row-to-gmail/`](./google-sheets-row-to-gmail) | Google Sheets -> Gmail | +| Chain | [`typeform-submission-to-gmail/`](./typeform-submission-to-gmail) | Typeform -> Gmail | + +## Where hospitality teams typically wire these + +- **Flagship intake**: Meta lead-ads webhook (booking inquiry, event inquiry forms). Every lead lands in a shared sheet the front-desk or events team already checks. +- **Sheet-as-trigger**: a booking or waitlist sheet gets a new row (added by staff, a POS integration, or another Zap), and the workflow emails the guest or the on-duty manager. +- **Chain**: a Typeform booking or feedback form triggers an immediate confirmation email to the respondent. + +## Patterns not yet covered + +Common shapes a hospitality team would want that don't have workflows yet: + +- **Facebook Lead Ads -> Mailchimp**: warm up event/booking inquiries in a nurture sequence — Mailchimp is hospitality's #5 app by usage. +- **Calendly/booking-tool -> Google Sheets + Discord**: reservation booked triggers a row plus a staff ping. +- **Guest checkout (Stripe/Square) -> feedback-request email**: post-stay NPS or review-request follow-up. + +PRs welcome. diff --git a/examples/by-domain/hospitality/facebook-lead-ads-to-google-sheets b/examples/by-domain/hospitality/facebook-lead-ads-to-google-sheets new file mode 120000 index 0000000..ad214ed --- /dev/null +++ b/examples/by-domain/hospitality/facebook-lead-ads-to-google-sheets @@ -0,0 +1 @@ +../../by-pattern/lead-routing/facebook-lead-ads-to-google-sheets \ No newline at end of file diff --git a/examples/by-domain/hospitality/google-sheets-row-to-gmail b/examples/by-domain/hospitality/google-sheets-row-to-gmail new file mode 120000 index 0000000..4d4ce50 --- /dev/null +++ b/examples/by-domain/hospitality/google-sheets-row-to-gmail @@ -0,0 +1 @@ +../../by-pattern/notify-on-event/google-sheets-row-to-gmail \ No newline at end of file diff --git a/examples/by-domain/hospitality/typeform-submission-to-gmail b/examples/by-domain/hospitality/typeform-submission-to-gmail new file mode 120000 index 0000000..0ded170 --- /dev/null +++ b/examples/by-domain/hospitality/typeform-submission-to-gmail @@ -0,0 +1 @@ +../../by-pattern/notify-on-event/typeform-submission-to-gmail \ No newline at end of file diff --git a/examples/by-domain/non-profit/README.md b/examples/by-domain/non-profit/README.md new file mode 100644 index 0000000..72dce12 --- /dev/null +++ b/examples/by-domain/non-profit/README.md @@ -0,0 +1,33 @@ +# Non-Profit + +Automations for non-profit and NGO teams: donor and volunteer intake, event registration, donor follow-up. + +## Recurring app stack + +**Gravity Forms, Eventbrite, Typeform, Google Sheets, Gmail.** Non-profits skew WordPress-native for their donation and volunteer forms, and lean on Eventbrite for fundraisers and galas more than any other vertical. + +## Canonical workflows + +Each entry is a filesystem symlink to the canonical directory under `by-pattern/`. Follow it to read the workflow source and README. + +| Shape | Workflow | Apps | +|---|---|---| +| WordPress intake | [`gravity-forms-to-google-sheets/`](./gravity-forms-to-google-sheets) | Gravity Forms -> Google Sheets | +| Event registration | [`eventbrite-to-google-sheets/`](./eventbrite-to-google-sheets) | Eventbrite -> Google Sheets | +| Chain | [`typeform-submission-to-gmail/`](./typeform-submission-to-gmail) | Typeform -> Gmail | + +## Where non-profit teams typically wire these + +- **WordPress intake**: a donation or volunteer-signup Gravity Form on the org's site logs every entry to a shared sheet the program team already works from. +- **Event registration**: a fundraiser or gala's Eventbrite listing feeds the same log-to-sheet shape, giving the events team one roster view. +- **Chain**: a Typeform intake or interest form (volunteer application, newsletter signup) sends an immediate thank-you email to the respondent. + +## Patterns not yet covered + +Common shapes a non-profit team would want that don't have workflows yet: + +- **Gravity Forms -> CRM upsert**: donation/volunteer form entry creates or updates a donor record in the org's CRM, with a donor-stage field set. +- **Google Sheets donor roster -> scheduled Gmail digest**: weekly summary of new donors/volunteers to the program lead. +- **CRM record change -> CRM update (donor lifecycle)**: donor-stage transitions triggering related record updates — a strong signal in non-profit usage data, but the CRM in question (Salesforce) is outside this corpus's current app coverage. + +PRs welcome. diff --git a/examples/by-domain/non-profit/eventbrite-to-google-sheets b/examples/by-domain/non-profit/eventbrite-to-google-sheets new file mode 120000 index 0000000..ae9f7bf --- /dev/null +++ b/examples/by-domain/non-profit/eventbrite-to-google-sheets @@ -0,0 +1 @@ +../../by-pattern/lead-routing/eventbrite-to-google-sheets \ No newline at end of file diff --git a/examples/by-domain/non-profit/gravity-forms-to-google-sheets b/examples/by-domain/non-profit/gravity-forms-to-google-sheets new file mode 120000 index 0000000..fc4e522 --- /dev/null +++ b/examples/by-domain/non-profit/gravity-forms-to-google-sheets @@ -0,0 +1 @@ +../../by-pattern/lead-routing/gravity-forms-to-google-sheets \ No newline at end of file diff --git a/examples/by-domain/non-profit/typeform-submission-to-gmail b/examples/by-domain/non-profit/typeform-submission-to-gmail new file mode 120000 index 0000000..0ded170 --- /dev/null +++ b/examples/by-domain/non-profit/typeform-submission-to-gmail @@ -0,0 +1 @@ +../../by-pattern/notify-on-event/typeform-submission-to-gmail \ No newline at end of file diff --git a/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/README.md b/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/README.md new file mode 100644 index 0000000..7c43606 --- /dev/null +++ b/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/README.md @@ -0,0 +1,33 @@ +# eventbrite-to-google-sheets + +A Zapier durable workflow that appends every new Eventbrite attendee as a row in a Google Sheet. Eventbrite doesn't crack the top 10 for most verticals, but it's a distinctive top-5 pattern for non-profits — fundraisers and galas run their registration through it more than any other vertical. + +## What it does + +- **Trigger:** a `{ attendeeId, eventName, name, email, ticketClass }` object delivered to the workflow input (validated by Zod). +- **Step 1: `append-row-`:** `GoogleSheetsV2CLIAPI.add_row`. Per-attendee step name keeps retries idempotent. + +## Parameters to edit before deploy + +| Constant | Purpose | How to determine the value | +|---|---|---| +| `GOOGLE_SHEETS_CONNECTION` | Deploy-time alias for the Google Sheets connection | `zapier-sdk list-connections` | +| `SPREADSHEET` | Target spreadsheet name or id | `zapier-sdk list-action-input-field-choices google-sheets write add_row spreadsheet` | +| `WORKSHEET` | Target worksheet/tab name | `zapier-sdk list-action-input-field-choices google-sheets write add_row worksheet` (depends on `SPREADSHEET`) | + +The row column names (`"Name"`, `"Email"`, ...) are `// dynamic` — they must match your worksheet's header row exactly. + +## Discovery + +```bash +zapier-sdk list-actions google-sheets --action-type write +zapier-sdk list-action-input-fields google-sheets write add_row + +zapier-sdk list-actions eventbrite +``` + +## Deploy + +See [`../../README.md#deploying-a-durable-workflow`](../../README.md#deploying-a-durable-workflow). + +Wire Eventbrite's `new_attendee_hook` ("New Attendee Registered") trigger to this workflow's endpoint. The attendee payload passes through as the workflow input. diff --git a/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/package.json b/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/package.json new file mode 100644 index 0000000..34d505f --- /dev/null +++ b/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/package.json @@ -0,0 +1,12 @@ +{ + "name": "eventbrite-to-google-sheets", + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "0.80.0", + "@zapier/zapier-durable": "0.6.1", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "latest" + } +} diff --git a/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/workflow.ts b/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/workflow.ts new file mode 100644 index 0000000..b9c9bf1 --- /dev/null +++ b/examples/by-pattern/lead-routing/eventbrite-to-google-sheets/workflow.ts @@ -0,0 +1,50 @@ +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +// Edit these three constants before deploying. See README.md for how to discover them. +const GOOGLE_SHEETS_CONNECTION = "google_sheets_primary"; +const SPREADSHEET = "Registrations"; +const WORKSHEET = "Sheet1"; + +const GOOGLE_SHEETS_APP_KEY = "GoogleSheetsV2CLIAPI"; + +const InputSchema = z.object({ + attendeeId: z.string(), + eventName: z.string(), + name: z.string(), + email: z.string().email(), + ticketClass: z.string().optional(), +}); +type Input = z.infer; + +export default defineDurable( + "eventbrite-to-google-sheets", + async (ctx, rawInput) => { + const { attendeeId, eventName, name, email, ticketClass } = InputSchema.parse(rawInput); + + await ctx.step(`append-row-${attendeeId}`, async () => + sdk.runAction({ + appKey: GOOGLE_SHEETS_APP_KEY, + actionType: "write", + actionKey: "add_row", + connection: GOOGLE_SHEETS_CONNECTION, + inputs: { + drive: "My Drive", + spreadsheet: SPREADSHEET, + worksheet: WORKSHEET, + // Row columns are dynamic — they must match your worksheet's header + // row exactly. + Name: name, + Email: email, + Event: eventName, + "Ticket Type": ticketClass, + }, + }), + ); + + return { rowAdded: true }; + }, +); diff --git a/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/README.md b/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/README.md new file mode 100644 index 0000000..0f8839b --- /dev/null +++ b/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/README.md @@ -0,0 +1,34 @@ +# facebook-lead-ads-to-google-sheets + +A Zapier durable workflow that appends every new Facebook Lead Ads lead as a row in a Google Sheet. This is the single highest-volume shape in Zapier's own usage data — the flagship "log the lead" pattern that shows up in nearly every vertical's top 3. + +## What it does + +- **Trigger:** a `{ leadgen_id, form_name, ad_name, campaign_name, field_data }` object delivered to the workflow input (validated by Zod) — the raw Meta Lead Ads webhook shape. +- **Step 1: `append-row-`:** `GoogleSheetsV2CLIAPI.add_row`. Maps the Meta field-data array onto named worksheet columns. Per-lead step name keeps retries idempotent. + +## Parameters to edit before deploy + +| Constant | Purpose | How to determine the value | +|---|---|---| +| `GOOGLE_SHEETS_CONNECTION` | Deploy-time alias for the Google Sheets connection | `zapier-sdk list-connections` | +| `SPREADSHEET` | Target spreadsheet name or id | `zapier-sdk list-action-input-field-choices google-sheets write add_row spreadsheet` | +| `WORKSHEET` | Target worksheet/tab name | `zapier-sdk list-action-input-field-choices google-sheets write add_row worksheet` (depends on `SPREADSHEET`) | +| `FIELD_EMAIL`, `FIELD_PHONE`, `FIELD_FIRST_NAME`, `FIELD_LAST_NAME` | Meta form field labels to pull from `field_data` | Match your Lead Ads form's field names exactly (case-insensitive) | + +The row column names (`"First Name"`, `"Email"`, ...) are `// dynamic` — they must match your worksheet's header row exactly. Adding, renaming, or removing a header column changes the accepted input shape. + +## Discovery + +```bash +zapier-sdk list-actions google-sheets --action-type write +zapier-sdk list-action-input-fields google-sheets write add_row + +zapier-sdk list-actions facebook-lead-ads +``` + +## Deploy + +See [`../../README.md#deploying-a-durable-workflow`](../../README.md#deploying-a-durable-workflow). + +Wire Facebook Lead Ads' `lead` webhook (or Zapier's polling trigger) to this workflow's endpoint. The raw lead payload passes through as the workflow input. diff --git a/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/package.json b/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/package.json new file mode 100644 index 0000000..ee208d8 --- /dev/null +++ b/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/package.json @@ -0,0 +1,12 @@ +{ + "name": "facebook-lead-ads-to-google-sheets", + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "0.80.0", + "@zapier/zapier-durable": "0.6.1", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "latest" + } +} diff --git a/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/workflow.ts b/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/workflow.ts new file mode 100644 index 0000000..69e17c6 --- /dev/null +++ b/examples/by-pattern/lead-routing/facebook-lead-ads-to-google-sheets/workflow.ts @@ -0,0 +1,71 @@ +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +// Edit these three constants before deploying. See README.md for how to discover them. +const GOOGLE_SHEETS_CONNECTION = "google_sheets_primary"; +const SPREADSHEET = "Leads"; +const WORKSHEET = "Sheet1"; + +const GOOGLE_SHEETS_APP_KEY = "GoogleSheetsV2CLIAPI"; + +// Meta lead field labels are set by the form owner. Edit if your form uses different names. +const FIELD_EMAIL = "email"; +const FIELD_PHONE = "phone_number"; +const FIELD_FIRST_NAME = "first_name"; +const FIELD_LAST_NAME = "last_name"; + +const InputSchema = z.object({ + leadgen_id: z.string(), + form_name: z.string().optional(), + ad_name: z.string().optional(), + campaign_name: z.string().optional(), + field_data: z.array( + z.object({ name: z.string(), values: z.array(z.string()) }), + ), +}); +type Input = z.infer; + +function pickField(fd: Input["field_data"], key: string): string | undefined { + return fd.find((f) => f.name.toLowerCase() === key.toLowerCase())?.values[0]; +} + +export default defineDurable( + "facebook-lead-ads-to-google-sheets", + async (ctx, rawInput) => { + const lead = InputSchema.parse(rawInput); + + const email = pickField(lead.field_data, FIELD_EMAIL); + const phone = pickField(lead.field_data, FIELD_PHONE); + const firstName = pickField(lead.field_data, FIELD_FIRST_NAME) ?? "Lead"; + const lastName = pickField(lead.field_data, FIELD_LAST_NAME) ?? "(from Meta)"; + + await ctx.step(`append-row-${lead.leadgen_id}`, async () => + sdk.runAction({ + appKey: GOOGLE_SHEETS_APP_KEY, + actionType: "write", + actionKey: "add_row", + connection: GOOGLE_SHEETS_CONNECTION, + inputs: { + drive: "My Drive", + spreadsheet: SPREADSHEET, + worksheet: WORKSHEET, + // Row columns are dynamic — they must match your worksheet's header + // row exactly. Verify names with list-action-input-fields against + // your connection (worksheet header changes alter this shape). + "First Name": firstName, + "Last Name": lastName, + Email: email, + Phone: phone, + Form: lead.form_name, + Ad: lead.ad_name, + Campaign: lead.campaign_name, + }, + }), + ); + + return { rowAdded: true }; + }, +); diff --git a/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/README.md b/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/README.md new file mode 100644 index 0000000..222c144 --- /dev/null +++ b/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/README.md @@ -0,0 +1,33 @@ +# gravity-forms-to-google-sheets + +A Zapier durable workflow that appends every new Gravity Forms entry as a row in a Google Sheet. Gravity Forms is the dominant WordPress form plugin behind donation and volunteer-signup intake, so this is the WordPress-ecosystem sibling to `typeform-to-google-sheets` — same destination, different intake. + +## What it does + +- **Trigger:** a `{ entryId, formTitle, fields }` object delivered to the workflow input (validated by Zod). `fields` is a flattened field-label -> value map. +- **Step 1: `append-row-`:** `GoogleSheetsV2CLIAPI.add_row`. Spreads `fields` onto worksheet columns alongside entry metadata. Per-entry step name keeps retries idempotent. + +## Parameters to edit before deploy + +| Constant | Purpose | How to determine the value | +|---|---|---| +| `GOOGLE_SHEETS_CONNECTION` | Deploy-time alias for the Google Sheets connection | `zapier-sdk list-connections` | +| `SPREADSHEET` | Target spreadsheet name or id | `zapier-sdk list-action-input-field-choices google-sheets write add_row spreadsheet` | +| `WORKSHEET` | Target worksheet/tab name | `zapier-sdk list-action-input-field-choices google-sheets write add_row worksheet` (depends on `SPREADSHEET`) | + +The `fields` spread is `// dynamic` — each key must match your worksheet's header row exactly. Gravity Forms' native field labels are set per-form by the site admin. + +## Discovery + +```bash +zapier-sdk list-actions google-sheets --action-type write +zapier-sdk list-action-input-fields google-sheets write add_row + +zapier-sdk list-actions gravity-forms +``` + +## Deploy + +See [`../../README.md#deploying-a-durable-workflow`](../../README.md#deploying-a-durable-workflow). + +Wire Gravity Forms' `getEntry` ("Form Submission") trigger to this workflow's endpoint, flattening the raw field-id-keyed entry to the `fields` map shape first (a Formatter/Code step ahead of this workflow, or equivalent transform on your trigger). diff --git a/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/package.json b/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/package.json new file mode 100644 index 0000000..6fd5415 --- /dev/null +++ b/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/package.json @@ -0,0 +1,12 @@ +{ + "name": "gravity-forms-to-google-sheets", + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "0.80.0", + "@zapier/zapier-durable": "0.6.1", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "latest" + } +} diff --git a/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/workflow.ts b/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/workflow.ts new file mode 100644 index 0000000..3c5bd25 --- /dev/null +++ b/examples/by-pattern/lead-routing/gravity-forms-to-google-sheets/workflow.ts @@ -0,0 +1,50 @@ +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +// Edit these three constants before deploying. See README.md for how to discover them. +const GOOGLE_SHEETS_CONNECTION = "google_sheets_primary"; +const SPREADSHEET = "Form Entries"; +const WORKSHEET = "Sheet1"; + +const GOOGLE_SHEETS_APP_KEY = "GoogleSheetsV2CLIAPI"; + +const InputSchema = z.object({ + entryId: z.string(), + formTitle: z.string(), + // Flattened field-label -> value map. Gravity Forms' raw webhook payload + // keys fields by numeric field id; flatten it to this shape before wiring + // the trigger to this workflow. + fields: z.record(z.string(), z.string()), +}); +type Input = z.infer; + +export default defineDurable( + "gravity-forms-to-google-sheets", + async (ctx, rawInput) => { + const { entryId, formTitle, fields } = InputSchema.parse(rawInput); + + await ctx.step(`append-row-${entryId}`, async () => + sdk.runAction({ + appKey: GOOGLE_SHEETS_APP_KEY, + actionType: "write", + actionKey: "add_row", + connection: GOOGLE_SHEETS_CONNECTION, + inputs: { + drive: "My Drive", + spreadsheet: SPREADSHEET, + worksheet: WORKSHEET, + "Entry ID": entryId, + Form: formTitle, + // Every other column is dynamic — one per Gravity Forms field + // label. Must match your worksheet's header row exactly. + ...fields, + }, + }), + ); + + return { rowAdded: true }; + }, +); diff --git a/examples/by-pattern/lead-routing/typeform-to-google-sheets/README.md b/examples/by-pattern/lead-routing/typeform-to-google-sheets/README.md new file mode 100644 index 0000000..0a5b1a3 --- /dev/null +++ b/examples/by-pattern/lead-routing/typeform-to-google-sheets/README.md @@ -0,0 +1,33 @@ +# typeform-to-google-sheets + +A Zapier durable workflow that appends every new Typeform submission as a row in a Google Sheet. The canonical **trigger-swap** sibling to `facebook-lead-ads-to-google-sheets` — same destination, different intake — useful for showing how the SDK isolates the trigger shape without touching downstream logic. + +## What it does + +- **Trigger:** a `{ responseId, formTitle, submittedAt, answers }` object delivered to the workflow input (validated by Zod). `answers` is a flattened question-title -> answer-text map. +- **Step 1: `append-row-`:** `GoogleSheetsV2CLIAPI.add_row`. Spreads `answers` onto worksheet columns alongside response metadata. Per-response step name keeps retries idempotent. + +## Parameters to edit before deploy + +| Constant | Purpose | How to determine the value | +|---|---|---| +| `GOOGLE_SHEETS_CONNECTION` | Deploy-time alias for the Google Sheets connection | `zapier-sdk list-connections` | +| `SPREADSHEET` | Target spreadsheet name or id | `zapier-sdk list-action-input-field-choices google-sheets write add_row spreadsheet` | +| `WORKSHEET` | Target worksheet/tab name | `zapier-sdk list-action-input-field-choices google-sheets write add_row worksheet` (depends on `SPREADSHEET`) | + +The `answers` spread is `// dynamic` — each key must match your worksheet's header row exactly. If your form's question set changes, the worksheet headers need to change with it. + +## Discovery + +```bash +zapier-sdk list-actions google-sheets --action-type write +zapier-sdk list-action-input-fields google-sheets write add_row + +zapier-sdk list-actions typeform +``` + +## Deploy + +See [`../../README.md#deploying-a-durable-workflow`](../../README.md#deploying-a-durable-workflow). + +Wire Typeform's `entries_resthook_v2` ("New Entry") webhook to this workflow's endpoint, flattening the raw answers array to the `answers` map shape first (a Formatter/Code step ahead of this workflow, or equivalent transform on your trigger). diff --git a/examples/by-pattern/lead-routing/typeform-to-google-sheets/package.json b/examples/by-pattern/lead-routing/typeform-to-google-sheets/package.json new file mode 100644 index 0000000..d044aa1 --- /dev/null +++ b/examples/by-pattern/lead-routing/typeform-to-google-sheets/package.json @@ -0,0 +1,12 @@ +{ + "name": "typeform-to-google-sheets", + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "0.80.0", + "@zapier/zapier-durable": "0.6.1", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "latest" + } +} diff --git a/examples/by-pattern/lead-routing/typeform-to-google-sheets/workflow.ts b/examples/by-pattern/lead-routing/typeform-to-google-sheets/workflow.ts new file mode 100644 index 0000000..3a5e6e8 --- /dev/null +++ b/examples/by-pattern/lead-routing/typeform-to-google-sheets/workflow.ts @@ -0,0 +1,52 @@ +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +// Edit these three constants before deploying. See README.md for how to discover them. +const GOOGLE_SHEETS_CONNECTION = "google_sheets_primary"; +const SPREADSHEET = "Form Responses"; +const WORKSHEET = "Sheet1"; + +const GOOGLE_SHEETS_APP_KEY = "GoogleSheetsV2CLIAPI"; + +const InputSchema = z.object({ + responseId: z.string(), + formTitle: z.string(), + submittedAt: z.string(), + // Flattened question-title -> answer-text map. Typeform's raw webhook + // payload nests each answer under `field` + a type-specific value key; + // flatten it to this shape before wiring the trigger to this workflow. + answers: z.record(z.string(), z.string()), +}); +type Input = z.infer; + +export default defineDurable( + "typeform-to-google-sheets", + async (ctx, rawInput) => { + const { responseId, formTitle, submittedAt, answers } = InputSchema.parse(rawInput); + + await ctx.step(`append-row-${responseId}`, async () => + sdk.runAction({ + appKey: GOOGLE_SHEETS_APP_KEY, + actionType: "write", + actionKey: "add_row", + connection: GOOGLE_SHEETS_CONNECTION, + inputs: { + drive: "My Drive", + spreadsheet: SPREADSHEET, + worksheet: WORKSHEET, + "Response ID": responseId, + Form: formTitle, + "Submitted At": submittedAt, + // Every other column is dynamic — one per Typeform question title. + // Must match your worksheet's header row exactly. + ...answers, + }, + }), + ); + + return { rowAdded: true }; + }, +); diff --git a/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/README.md b/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/README.md new file mode 100644 index 0000000..03bdb87 --- /dev/null +++ b/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/README.md @@ -0,0 +1,30 @@ +# google-sheets-row-to-gmail + +A Zapier durable workflow that sends a Gmail notification whenever a new row lands in a Google Sheet. Inverts the sheet-as-log pattern (`facebook-lead-ads-to-google-sheets`, `typeform-to-google-sheets`) into a sheet-as-trigger pattern — useful for lightweight CRMs or trackers built entirely on a spreadsheet, where a human still needs a nudge outside the sheet. + +## What it does + +- **Trigger:** a `{ rowId, recipientEmail, rowSummary }` object delivered to the workflow input (validated by Zod). `rowSummary` is a pre-formatted digest of the row's columns. +- **Step 1: `send-notification-`:** `GmailCLIAPI.message` sends the digest to the recipient. Per-row step name keeps retries idempotent. + +## Parameters to edit before deploy + +| Constant | Purpose | How to determine the value | +|---|---|---| +| `GMAIL_CONNECTION` | Deploy-time alias for the Gmail connection | `zapier-sdk list-connections` | +| `EMAIL_SUBJECT` | Subject line | Edit inline | + +## Discovery + +```bash +zapier-sdk list-actions gmail +zapier-sdk list-action-input-fields gmail write message + +zapier-sdk list-actions google-sheets +``` + +## Deploy + +See [`../../README.md#deploying-a-durable-workflow`](../../README.md#deploying-a-durable-workflow). + +Wire Google Sheets' `new_row` ("New Spreadsheet Row") trigger to this workflow's endpoint, formatting the row's columns into `rowSummary` first (a Formatter/Code step ahead of this workflow, or equivalent transform on your trigger). diff --git a/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/package.json b/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/package.json new file mode 100644 index 0000000..99b58da --- /dev/null +++ b/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/package.json @@ -0,0 +1,12 @@ +{ + "name": "google-sheets-row-to-gmail", + "type": "module", + "dependencies": { + "@zapier/zapier-sdk": "0.80.0", + "@zapier/zapier-durable": "0.6.1", + "zod": "4.4.3" + }, + "devDependencies": { + "typescript": "latest" + } +} diff --git a/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/workflow.ts b/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/workflow.ts new file mode 100644 index 0000000..de9eb8e --- /dev/null +++ b/examples/by-pattern/notify-on-event/google-sheets-row-to-gmail/workflow.ts @@ -0,0 +1,42 @@ +import { defineDurable } from "@zapier/zapier-durable"; +import { createZapierSdk } from "@zapier/zapier-sdk"; +import { z } from "zod"; + +const sdk = createZapierSdk(); + +const GMAIL_CONNECTION = "gmail_primary"; +const EMAIL_SUBJECT = "New row added"; + +const GMAIL_APP_KEY = "GoogleMailV2CLIAPI"; + +const InputSchema = z.object({ + rowId: z.string(), + recipientEmail: z.string().email(), + // Formatted "Column: value" text assembled upstream (a Formatter/Code step + // ahead of this workflow, or equivalent transform on your trigger). + rowSummary: z.string(), +}); +type Input = z.infer; + +export default defineDurable( + "google-sheets-row-to-gmail", + async (ctx, rawInput) => { + const { rowId, recipientEmail, rowSummary } = InputSchema.parse(rawInput); + + await ctx.step(`send-notification-${rowId}`, async () => + sdk.runAction({ + appKey: GMAIL_APP_KEY, + actionType: "write", + actionKey: "message", + connection: GMAIL_CONNECTION, + inputs: { + to: [recipientEmail], + subject: EMAIL_SUBJECT, + body: rowSummary, + }, + }), + ); + + return { sent: true }; + }, +);