Setting Up Automated CRM Workflows with 0nMCP in Under 15 Minutes

Most CRM automation setups look the same: you open a visual builder, drag some boxes around, connect them with arrows, hit save, and pray it works when a real contact hits the pipeline. Then six months later, nobody remembers why the workflow exists, what it does, or how to change it without breaking everything. There's a better way. With [0nMCP](/) and its 245 dedicated CRM tools, you can define automations as code — portable, version-controlled, and composable. And because 0nMCP uses [.0n SWITCH files](/0n-standard) instead of proprietary workflow formats, your automations are human-readable configuration, not locked inside a platform. This tutorial walks you through building three real CRM workflows from scratch. By the end, you'll have a working automation pipeline that creates contacts, enrolls them in workflows, and notifies your team — all running in under 15 minutes. ## What You'll Build 1. **Lead Intake Pipeline** — New form submission → create contact → tag → assign to pipeline stage 2. **Follow-Up Automator** — Opportunity stage change → send personalized email → notify sales rep on Slack 3. **Nightly Sync** — Batch-pull contacts updated in the last 24 hours → sync to Supabase for reporting Each one uses a different execution pattern from 0nMCP's patent-pending Three-Level Execution system: Pipeline (sequential), Assembly Line (parallel with dependencies), and Radial Burst (fan-out). ## Prerequisites - Node.js 18+ - 0nMCP installed (`npm install -g 0nmcp`) - CRM API credentials (your location-level API key or OAuth token) - Optional: [Slack](/integrations/slack) and [Supabase](/integrations/supabase) credentials for workflows 2 and 3 If you haven't connected your services yet, head to [Turn It 0n](/turn-it-on) and follow the guided setup. The CRM connection takes about 90 seconds — see the [CRM integration page](/turn-it-on/crm) for specifics. ## Step 1: Connect Your CRM (2 minutes) First, import your CRM credentials into 0nMCP's secure credential store: ```bash 0nmcp engine import ``` This scans your environment for API keys and maps them to 0nMCP's 54 supported services. For the CRM specifically, you need two values: ```bash # In your .env or entered interactively CRM_API_KEY=your-api-key-here CRM_LOCATION_ID=your-location-id ``` Verify the connection works: ```bash 0nmcp engine verify --service crm ``` You should see a green checkmark and your location name. If not, double-check that your API key has the right scopes — 0nMCP's CRM module needs contacts, opportunities, and conversations permissions at minimum. The credentials are stored in `~/.0n/connections/crm.0n` using AES-256-GCM encryption with hardware fingerprint binding. They never leave your machine in plaintext. For more on how this works, see our [vault documentation](/security/vault). ## Step 2: Build the Lead Intake Pipeline (4 minutes) Let's start with the most common automation: turning a form submission into a fully tagged, pipeline-assigned contact. Create a file called `lead-intake.0n`: ```yaml name: lead-intake version: 1.0.0 trigger: type: webhook path: /intake method: POST steps: - id: create_contact tool: crm_contacts_create inputs: firstName: "{{trigger.body.first_name}}" lastName: "{{trigger.body.last_name}}" email: "{{trigger.body.email}}" phone: "{{trigger.body.phone}}" source: "Website Form" tags: - "new-lead" - "{{trigger.body.form_name}}" - id: add_to_pipeline tool: crm_opportunities_create inputs: contactId: "{{steps.create_contact.output.contact.id}}" pipelineId: "your-pipeline-id" stageId: "your-first-stage-id" name: "{{steps.create_contact.output.contact.contactName}} - New Lead" monetaryValue: 0 - id: add_note tool: crm_contacts_add_note inputs: contactId: "{{steps.create_contact.output.contact.id}}" body: "Auto-created from {{trigger.body.form_name}} on {{system.date}}" ``` A few things to notice: - **`{{trigger.body.*}}`** pulls values from the incoming webhook POST body. 0nMCP's template engine handles the variable resolution automatically — see the [.0n standard](/0n-standard) for the full expression syntax. - **`{{steps.create_contact.output.*}}`** chains outputs from previous steps. This is the Pipeline execution pattern: each step runs sequentially, and later steps can reference earlier outputs. - **`{{system.date}}`** is a built-in system variable. The resolution order is: `system.*` > `launch.*` > `inputs.*` > `step.output.*`. Deploy it: ```bash 0nmcp run lead-intake.0n --serve --port 3001 ``` This starts an HTTP server with your webhook endpoint at `http://localhost:3001/intake`. Point your form to this URL, and every submission creates a contact, adds them to your pipeline, and attaches a note — zero manual steps. ### What about the CRM tools being used here? 0nMCP ships with 245 CRM tools across 12 categories. The three we used (`crm_contacts_create`, `crm_opportunities_create`, `crm_contacts_add_note`) are part of the contacts module (23 tools) and opportunities module (14 tools). The full list includes everything from conversation management (13 tools) to calendar booking (27 tools) to social media posting (35 tools). You can explore all of them on the [CRM integration page](/integrations). ## Step 3: Build the Follow-Up Automator (4 minutes) Now let's build something that reacts to pipeline changes. When an opportunity moves to the "Proposal Sent" stage, we want to: 1. Fetch the contact details 2. Send them a personalized follow-up email 3. Notify the assigned sales rep on Slack Steps 2 and 3 are independent of each other — they both depend on step 1, but they don't depend on each other. This is the Assembly Line pattern: sequential where needed, parallel where possible. Create `follow-up.0n`: ```yaml name: follow-up-automator version: 1.0.0 trigger: type: webhook path: /opportunity-update method: POST filter: body.stageId: "your-proposal-sent-stage-id" steps: - id: get_contact tool: crm_contacts_get inputs: contactId: "{{trigger.body.contactId}}" - id: send_email tool: crm_conversations_send_email depends_on: [get_contact] inputs: contactId: "{{trigger.body.contactId}}" subject: "Next steps for your project, {{steps.get_contact.output.contact.firstName}}" body: "Hi {{steps.get_contact.output.contact.firstName}},\n\nThanks for reviewing our proposal. I wanted to follow up and see if you have any questions.\n\nLooking forward to hearing from you." emailFrom: "sales@yourcompany.com" - id: notify_slack tool: slack_send_message depends_on: [get_contact] inputs: channel: "#sales-notifications" text: "Proposal sent to {{steps.get_contact.output.contact.contactName}} ({{steps.get_contact.output.contact.email}}). Follow-up email dispatched automatically." ``` The key detail is the `depends_on` field. Both `send_email` and `notify_slack` depend on `get_contact`, but not on each other. 0nMCP's execution engine recognizes this and runs them in parallel after the contact fetch completes. This is faster than sequential execution and safer than full fan-out — the Assembly Line pattern gives you parallelism with dependency awareness. The `filter` field on the trigger means this workflow only fires when the stage ID matches. Every other opportunity update gets ignored at the trigger level, before any tools execute. To connect this to your CRM, configure a webhook in your CRM settings that fires on opportunity stage changes and points to your 0nMCP server. 0nMCP supports HMAC verification for CRM webhooks out of the box — no extra configuration needed. ## Step 4: Build the Nightly Sync (3 minutes) The final workflow uses the Radial Burst pattern: fetch a batch of contacts, then fan out independent operations across all of them simultaneously. Create `nightly-sync.0n`: ```yaml name: nightly-contact-sync version: 1.0.0 trigger: type: schedule cron: "0 2 * * *" # 2 AM daily steps: - id: fetch_updated tool: crm_contacts_search inputs: query: "" filters: - field: dateUpdated operator: gte value: "{{system.date_minus_1d}}" limit: 100 - id: sync_to_supabase tool: supabase_upsert depends_on: [fetch_updated] for_each: "{{steps.fetch_updated.output.contacts}}" execution: radial_burst inputs: table: crm_contacts_mirror record: crm_id: "{{item.id}}" email: "{{item.email}}" name: "{{item.contactName}}" tags: "{{item.tags}}" updated_at: "{{item.dateUpdated}}" onConflict: "crm_id" ``` The `for_each` directive iterates over the contacts array, and `execution: radial_burst` tells 0nMCP to fire all upserts concurrently rather than one at a time. For 100 contacts, this typically completes in under 2 seconds versus 15+ seconds sequentially. Radial Burst is the most aggressive execution pattern — it's designed for independent, idempotent operations where order doesn't matter. Rate limiting is handled automatically by 0nMCP's token bucket system, so you won't accidentally hammer the [Supabase](/turn-it-on/supabase) API. ## Step 5: Run Everything Together (2 minutes) You can run all three workflows from a single 0nMCP server: ```bash 0nmcp serve --port 3001 \ --workflow lead-intake.0n \ --workflow follow-up.0n \ --workflow nightly-sync.0n ``` This gives you: - `POST /intake` — lead intake webhook - `POST /opportunity-update` — opportunity stage change webhook - Cron job at 2 AM for the nightly sync - Health check at `GET /health` All three workflows are defined in plain text files that you can commit to git, review in pull requests, and deploy anywhere Node.js runs. ## Why This Beats Visual Workflow Builders If you've used tools like [Zapier or Make](/compare) for CRM automation, you've probably hit the same walls: - **Debugging is painful.** When a visual workflow fails, you're clicking through execution logs one node at a time. With .0n files, you `grep` your logs like any other application. - **Version control is impossible.** Visual builders don't diff well. SWITCH files are YAML — every change shows up cleanly in `git diff`. - **Composition doesn't exist.** Try making one Zapier zap call another. With 0nMCP, workflows can import and reference each other. - **You're paying per execution.** Most visual builders charge per task or step. 0nMCP runs on your infrastructure at whatever scale you need. 0nMCP gives you 870+ tools across 54 services — and the CRM alone accounts for 245 of those tools across contacts, conversations, calendars, opportunities, invoices, payments, products, locations, social media, users, and custom objects. That's not an abstraction layer with limited coverage. It's the full API surface, accessible through a single protocol. ## What's Next You've now got three production-ready CRM workflows running locally. From here, you can: - **Add error handling** with `on_error` blocks in your SWITCH files - **Chain workflows** by having one workflow trigger another via internal webhooks - **Add encryption** using [0nVault](/security/vault) to protect sensitive contact data at rest - **Deploy to production** by running `0nmcp serve` behind your preferred reverse proxy - **Explore more tools** — browse the full [integration catalog](/integrations) or check out the [examples page](/examples) for more workflow patterns If you get stuck, the [community forum](/forum) is active and full of people building real automations. And if you want to understand the underlying protocol that makes all of this work, start with the [MCP Protocol glossary entry](/glossary/mcp-protocol) and the [AI orchestration overview](/glossary/ai-orchestration). The entire point of 0nMCP is that your automations should be describable, portable, and auditable. No black boxes. No vendor lock-in. Just configuration files that do exactly what they say. Now go automate something.