---
name: inbound-speed-to-lead-after-hours
description: "Reply to demo requests within minutes at night and on weekends: enrich and screen the lead in Clay, send a guarded first reply, and hand off in Slack. Use when demo requests that come in at night or over the weekend wait until a rep logs on, so the buyer who was ready to talk on Friday evening gets a first reply on Monday, often after they have booked with someone else."
---

# Answer Demo Requests Within Minutes, Even After Hours

Demo requests that come in at night or over the weekend wait until a rep logs on, so the buyer who was ready to talk on Friday evening gets a first reply on Monday, often after they have booked with someone else.

## When to use this skill

- The user wants to set up or run this workflow: "Answer Demo Requests Within Minutes, Even After Hours".
- The user describes this problem: Demo requests that come in at night or over the weekend wait until a rep logs on, so the buyer who was ready to talk on Friday evening gets a first reply on Monday, often after they have booked with someone else.

## At a glance

- Category: Inbound
- Difficulty: Advanced
- Time saved: 3 hours per week. This figure is an estimate, not a measured result.

## Tools

Options separated by "or" are alternatives; use whichever the user already has.

- HubSpot or Salesforce
- Clay
- OpenAI API
- Gmail or Outlook
- Zapier or Make
- Slack

## Steps

1. Measure the baseline first: export the last 60 to 90 days of demo requests with the form submission time and the time of the first outbound email or call logged on the contact, then calculate median minutes to first reply split into business hours versus nights and weekends, and keep that sheet for the comparison
2. Send demo requests and the high-intent forms you choose (contact sales, pricing page) to a Clay webhook from a HubSpot workflow, a Salesforce flow, or a Zapier or Make relay, with email, name, company, form name, message, submitted_at in UTC, and the territory's round-robin booking link from HubSpot meetings, Calendly, or Chili Piper
3. Suppress before anything is sent: look up the email and company domain in the CRM, and never let the agent reply to existing customers (route them to the CSM or support queue), contacts with an open deal (alert the owner instead), competitors, students, job seekers, or support requests; enrich everyone else with title, seniority, headcount, industry, and country
4. Classify with an AI step that returns fit tier (A, B, or C against your written ICP), request type, escalation topics (pricing, discounts, contract terms, security or compliance questions, legal, refunds, complaints, integration commitments), and should_reply true or false; if any field comes back empty, send nothing and post the lead to Slack for a person
5. Write and send a 3 to 4 sentence reply from a shared sales inbox that refers to what they asked, offers the booking link, says a teammate will answer any escalation topic during business hours, and signs as the team rather than a named rep; the agent must never quote prices or discounts, promise features outside your approved capabilities list, commit to dates, or answer security, legal, or contract questions. Send it through Gmail or Outlook "Send Email" in Zapier or Make, or a Clay email campaign on the "As fast as possible" schedule
6. Create or update the CRM contact with fit tier, request type, escalation topics, agent reply text, first_reply_at, and reply_minutes (submitted_at to sent_at), let assignment rules set the owner, and create a task due at the start of the owner's next workday
7. Post the handoff to the owner in Slack: name, title, company, fit tier, what they asked, the reply the agent sent, whether a meeting is booked, and any escalation topic; Slack notification schedules keep it quiet overnight. Each week, compare median reply_minutes and booked meetings from after-hours leads against the baseline

## Template outline

Field names, thresholds, and message text are starting points to adapt.

~~~text
Baseline (before launch):
- Last 60 to 90 days of demo requests: submitted_at, first outbound email or call time
- Median minutes to first reply: business hours vs nights and weekends

Clay Workflow (webhook trigger, one run per qualifying form fill):
1. Payload: email, first_name, last_name, company, form_name, message, submitted_at (UTC), booking_link
2. CRM lookup (email, domain): is_customer, open_deal_stage, owner_email
   - Customer: no agent reply; alert CSM or support queue
   - Open deal: no agent reply; alert owner
3. Enrich: title, seniority, company_name, employee_count, industry, country
4. AI triage: fit_tier (A/B/C), request_type, escalation_topics, should_reply
   - should_reply = false: support, job, partnership, competitor, student, spam
   - Any empty field: do not send; Slack to a person
5. AI reply (only if should_reply):
   - 3 to 4 sentences, their words, booking link, signed "{{name}}, {{company}} team"
   - Escalation topic present: "A teammate will answer that during business hours"
6. Send: Gmail or Outlook Send Email (Zapier or Make), or Clay email campaign "As fast as possible"
7. CRM update: fit tier, request type, escalation topics, agent reply, first_reply_at, reply_minutes; task for owner at start of next workday
8. Slack to owner: "{{first_name}} {{last_name}}, {{title}} at {{company_name}} ({{fit_tier}}) asked: {{message}}. Agent replied at {{sent_at}} with the booking link. Escalation: {{escalation_topics}}. Meeting booked: {{yes_no}}."

Guardrails (the agent never):
- Quotes prices, discounts, trials, or contract terms
- Promises features outside {{approved_capabilities}} or delivery dates
- Answers security, compliance, legal, or refund questions
- Replies to customers, open deals, or anyone on a do-not-contact list
- Claims to be a person

Weekly: median reply_minutes and after-hours meetings booked vs baseline; escalations and opt-outs
Output: every qualifying demo request gets a guarded, relevant reply within minutes at any hour, and the owner starts the day with full context
~~~

## Optional: build it with the Clay CLI

This builds the after-hours responder as a Clay workflow with a webhook trigger. Each demo request or high-intent form fill becomes one run: check the CRM for customers and open deals, enrich the lead, classify fit and escalation topics, write a guarded first reply with a booking link, send it, write everything to the CRM, and post a handoff for the owner in Slack. The Clay CLI cannot create tables, so this build uses a workflow.

Written against Clay CLI v1.3.1. Run these commands only in the user's own Clay workspace, logged in with their own account, after they confirm. Items marked VERIFY are config shapes the CLI help does not document; check them in the workspace before relying on them.

### Prerequisites

- Install the Clay CLI: Use version 1.3.1 or later, installed from Clay's own instructions. This page does not include an install command because the CLI reference it was written from does not list one. You also need jq, which the script uses to read the JSON the CLI prints.
- Log in to your workspace: Browser login is the default. Use the device flow on a headless machine. A static key in the CLAY_API_KEY environment variable also works. Workflow commands need the terracotta:cli scope, so an auth error that names it means your login cannot manage workflows.
  - Log in with a browser: `clay login`
  - Log in on a headless machine: `clay login --device`
  - Confirm who you are logged in as: `clay whoami`
- Pick the workspace to build in: The workflow is created in your active workspace. Switch first if you belong to more than one.
  - List workspaces: `clay workspaces list`
  - Show the active workspace: `clay workspaces current`
  - Switch workspace: `clay workspaces switch <workspaceId>`
- Find your action keys: Action package ids and keys are specific to your workspace. Save the catalog, search it for your CRM lookup action, person and company enrichment, an AI text action, an email send action (or an HTTP request to a relay), your CRM create or update action, and Slack, then paste their ids into the top of the script. Skip "clay workflows actions test": it spends credits.
  - Save the action catalog: `clay workflows actions list > catalog.json`
  - Search the catalog: `grep -Eio '"[^"]*(hubspot|salesforce|lookup|enrich|use ai|generate|gmail|outlook|send email|http|slack)[^"]*"' catalog.json | sort -u`
  - See an action's inputs and outputs: `clay workflows actions schema <packageId> <actionKey>`
- Decide what sends form fills to the webhook: Send only demo requests and the high-intent forms you choose (contact sales, pricing page), from a HubSpot workflow or Salesforce flow where your edition can call a webhook, or from a Zapier or Make relay on new form submissions. Include the submission timestamp so response time can be measured.
- Create the CRM fields and the approved list: Add contact fields for fit tier, request type, escalation topics, agent reply text, first reply at, and reply minutes. Write the approved capabilities list and the booking link per territory before you turn the workflow on; the reply prompt only uses what you give it.

### Workflow: Inbound speed-to-lead

Trigger (webhook): A CRM workflow or a Zapier or Make relay POSTs one JSON object per qualifying form fill to the trigger's webhook URL. The fields arrive as run inputs as-is. VERIFY: Webhook actions in HubSpot workflows and outbound calls from Salesforce flows depend on your CRM edition. A relay works when the CRM cannot call a webhook directly.

Inputs (webhook payload fields):

- email: Work email from the form (example: jane@example.com)
- first_name: First name (example: Jane)
- last_name: Last name (example: Doe)
- company: Company from the form (example: Example Co)
- form_name: Which form was submitted (example: Request a demo)
- message: Free-text answer (example: We want to route inbound leads to 12 reps by territory.)
- submitted_at: Submission time in UTC (ISO 8601) (example: 2026-10-17T23:42:00Z)
- booking_link: Round-robin booking link for the territory (example: https://meetings.example.com/sales-team)

1. Check customers and open deals (action): Looks up the contact by email and the company by domain, and returns lifecycle or account type, open deal and stage, and owner.
   - Inputs: email
   - Outputs: contact_id, is_customer, open_deal_stage, owner_email
   - VERIFY: Customers and contacts with an open deal should not get an agent reply. That needs a run condition on steps 3 to 5, which the CLI help does not document, so set it in the Clay UI. Steps 6 and 7 still run so the owner hears about it.
2. Enrich the lead (action): Adds title, seniority, company name, headcount, industry, and country from the work email.
   - Inputs: email, company
   - Outputs: title, company_name, employee_count, industry, country
3. Classify fit and escalations (action): An AI text action returns fit tier, request type, escalation topics, and whether the agent should reply at all.
   - Inputs: form_name, message, title, company_name, employee_count, industry, country
   - Outputs: fit_tier, request_type, escalation_topics, should_reply, reason
   - Prompt:

~~~text
Classify this inbound form fill for {{your_company}}.
Form: {{form_name}}. Message: {{message}}
Person: {{title}} at {{company_name}} ({{employee_count}} employees, {{industry}}, {{country}}).
Our ICP: {{your_icp}}
Return JSON with:
- fit_tier: A, B, or C against the ICP
- request_type: demo, pricing, support, partnership, job, or other
- escalation_topics: any of pricing, discount, contract, security, compliance, legal, refund, complaint, integration_commitment, or empty
- should_reply: false for support, job, partnership, competitors, students, spam, or an empty message with no company; otherwise true
- reason: one sentence
~~~

4. Write the first reply (action): An AI text action writes a short reply with the booking link that follows the guardrails in the prompt. Runs only when should_reply is true.
   - Inputs: first_name, message, booking_link, escalation_topics
   - Outputs: reply_subject, reply_body
   - Prompt:

~~~text
Write a first reply from {{reply_sender_name}} at {{your_company}} to {{first_name}}, who asked: {{message}}
Rules:
- 3 or 4 short sentences, plain text, no em dashes
- Refer to what they asked about in their own terms
- Offer this booking link for a call: {{booking_link}}
- If escalation_topics is not empty ({{escalation_topics}}), say a teammate will answer that part during business hours; do not answer it
- Never mention prices, discounts, trials, contract terms, delivery dates, security or compliance claims, or features not in this approved list: {{approved_capabilities}}
- Do not claim to be a person; sign as "{{reply_sender_name}}, {{your_company}} team"
~~~

5. Send the reply (action): Sends the reply from a shared sales inbox or the assistant address and records the send time.
   - Inputs: email, reply_subject, reply_body
   - Outputs: sent_at
   - VERIFY: Check your catalog for a Gmail or Outlook send action. If there is none, POST to a Zapier or Make relay that uses Gmail or Outlook "Send Email", or add the lead to a Clay email campaign set to the "As fast as possible" schedule.
6. Update the CRM record (action): Creates or updates the contact with fit tier, request type, escalation topics, agent reply, first reply at, and reply minutes, and lets assignment rules set the owner when there is none.
   - Inputs: contact_id, email, fit_tier, request_type, escalation_topics, reply_body, sent_at, submitted_at
   - Outputs: CRM record
7. Post the handoff in Slack (action): Messages the owner with who asked, what they asked, what the agent sent, and any escalation, so they pick it up first thing.
   - Inputs: owner_email, first_name, last_name, title, company_name, fit_tier, message, reply_body, escalation_topics, open_deal_stage, is_customer
   - Outputs: Slack message
   - VERIFY: Direct messages need the Slack user, not the email. Check whether your Slack action looks users up by email or needs a separate lookup step.

### Script: build-speed-to-lead.sh

Creates the workflow, the webhook trigger, and seven steps, then pauses so you can map step inputs and the run conditions. It validates the graph, offers one test run, and asks before publishing.

~~~bash
#!/usr/bin/env bash
# Builds the "Inbound speed-to-lead" Clay workflow
# in YOUR Clay workspace, using YOUR Clay CLI login.
# Written against Clay CLI v1.3.1. Not run or tested by GTM Playbooks.
# Lines tagged VERIFY use a shape the CLI help does not document.
set -euo pipefail

# Copy these from catalog.json (see "Find your action keys").
LOOKUP_PACKAGE_ID="" # HubSpot or Salesforce: lookup record
LOOKUP_ACTION_KEY=""
ENRICH_PACKAGE_ID="" # Enrich person from work email
ENRICH_ACTION_KEY=""
TRIAGE_PACKAGE_ID="" # Use AI or another AI text action
TRIAGE_ACTION_KEY=""
WRITER_PACKAGE_ID="" # Use AI or another AI text action
WRITER_ACTION_KEY=""
SENDER_PACKAGE_ID="" # Gmail or Outlook: send email, or HTTP request to a relay
SENDER_ACTION_KEY=""
CRM_PACKAGE_ID=""    # HubSpot or Salesforce: create or update record
CRM_ACTION_KEY=""
SLACK_PACKAGE_ID=""  # Slack: send message
SLACK_ACTION_KEY=""

# One input to test before real traffic. Use yourself or a teammate.
TEST_ROW='{"email":"","first_name":"","last_name":"","company":"","form_name":"","message":"","submitted_at":"","booking_link":""}'

for v in LOOKUP_PACKAGE_ID LOOKUP_ACTION_KEY ENRICH_PACKAGE_ID ENRICH_ACTION_KEY TRIAGE_PACKAGE_ID TRIAGE_ACTION_KEY WRITER_PACKAGE_ID WRITER_ACTION_KEY SENDER_PACKAGE_ID SENDER_ACTION_KEY CRM_PACKAGE_ID CRM_ACTION_KEY SLACK_PACKAGE_ID SLACK_ACTION_KEY; do
  [[ -n "${!v}" ]] || { echo "Set $v at the top of this script." >&2; exit 1; }
done
command -v clay > /dev/null || { echo "Clay CLI not found on PATH." >&2; exit 1; }
command -v jq > /dev/null || { echo "jq is required to read CLI output." >&2; exit 1; }
clay whoami > /dev/null || { echo "Not logged in. Run: clay login" >&2; exit 1; }

# VERIFY: only "workflows create" documents an "id" field in its output.
read_id() {
  local id
  id=$(jq -r '.id // empty' "$1")
  [[ -n "$id" ]] || { echo "No id found in $1. Open it and adjust read_id." >&2; exit 1; }
  echo "$id"
}

mkdir -p clay-speed-to-lead
cd clay-speed-to-lead

echo "1/11 Create the workflow"
clay workflows create --name "Inbound speed-to-lead" > workflow.json
WF_ID=$(read_id workflow.json)
WF_URL=$(jq -r '.url // empty' workflow.json)

echo "2/11 Add the webhook trigger"
clay workflows triggers create "$WF_ID" --input '{"triggerType":"webhook","inputSchema":{"type":"object"}}' > trigger.json
TRG_ID=$(read_id trigger.json)

echo "3/11 Step 1: check customers and open deals"
# VERIFY: help shows nodeType and name only; actionPackageId and actionKey are a guess.
STEP1=$(jq -nc --arg p "$LOOKUP_PACKAGE_ID" --arg k "$LOOKUP_ACTION_KEY" '{nodeType: "tool", name: "Check customers and open deals", actionPackageId: $p, actionKey: $k}')
clay workflows nodes create "$WF_ID" --input "$STEP1" > step1.json
STEP1_ID=$(read_id step1.json)

echo "4/11 Step 2: enrich the lead"
# VERIFY: action fields inside "step" are inferred from the documented utility example.
STEP2=$(jq -nc --arg n "$STEP1_ID" --arg p "$ENRICH_PACKAGE_ID" --arg k "$ENRICH_ACTION_KEY" '{target: {type: "after-node", nodeId: $n}, step: {kind: "action", actionPackageId: $p, actionKey: $k}}')
clay workflows nodes insert "$WF_ID" --input "$STEP2" > step2.json
STEP2_ID=$(read_id step2.json)

echo "5/11 Step 3: classify fit and escalations"
# VERIFY: same inferred insert shape as step 2.
STEP3=$(jq -nc --arg n "$STEP2_ID" --arg p "$TRIAGE_PACKAGE_ID" --arg k "$TRIAGE_ACTION_KEY" '{target: {type: "after-node", nodeId: $n}, step: {kind: "action", actionPackageId: $p, actionKey: $k}}')
clay workflows nodes insert "$WF_ID" --input "$STEP3" > step3.json
STEP3_ID=$(read_id step3.json)

echo "6/11 Step 4: write the first reply"
# VERIFY: same inferred insert shape as step 2.
STEP4=$(jq -nc --arg n "$STEP3_ID" --arg p "$WRITER_PACKAGE_ID" --arg k "$WRITER_ACTION_KEY" '{target: {type: "after-node", nodeId: $n}, step: {kind: "action", actionPackageId: $p, actionKey: $k}}')
clay workflows nodes insert "$WF_ID" --input "$STEP4" > step4.json
STEP4_ID=$(read_id step4.json)

echo "7/11 Step 5: send the reply"
# VERIFY: same inferred insert shape as step 2.
STEP5=$(jq -nc --arg n "$STEP4_ID" --arg p "$SENDER_PACKAGE_ID" --arg k "$SENDER_ACTION_KEY" '{target: {type: "after-node", nodeId: $n}, step: {kind: "action", actionPackageId: $p, actionKey: $k}}')
clay workflows nodes insert "$WF_ID" --input "$STEP5" > step5.json
STEP5_ID=$(read_id step5.json)

echo "8/11 Step 6: update the crm record"
# VERIFY: same inferred insert shape as step 2.
STEP6=$(jq -nc --arg n "$STEP5_ID" --arg p "$CRM_PACKAGE_ID" --arg k "$CRM_ACTION_KEY" '{target: {type: "after-node", nodeId: $n}, step: {kind: "action", actionPackageId: $p, actionKey: $k}}')
clay workflows nodes insert "$WF_ID" --input "$STEP6" > step6.json
STEP6_ID=$(read_id step6.json)

echo "9/11 Step 7: post the handoff in slack"
# VERIFY: same inferred insert shape as step 2.
STEP7=$(jq -nc --arg n "$STEP6_ID" --arg p "$SLACK_PACKAGE_ID" --arg k "$SLACK_ACTION_KEY" '{target: {type: "after-node", nodeId: $n}, step: {kind: "action", actionPackageId: $p, actionKey: $k}}')
clay workflows nodes insert "$WF_ID" --input "$STEP7" > step7.json
clay workflows graph get "$WF_ID" --mode full > graph.json

echo "10/11 Map step inputs"
cat <<EOF

Step inputs are specific to your workspace. Map them before validating:
  Step 1 lookup:  email (and company domain)
  Step 2 enrich:  email, company
  Step 3 triage:  form_name, message, enrichment fields, the prompt with your ICP
  Step 4 reply:   first_name, message, booking_link, escalation_topics,
                  and the prompt with your approved capabilities list
  Step 5 send:    email, reply subject and body
  Step 6 CRM:     contact id, fit tier, request type, escalations, reply,
                  first reply at = sent_at, reply minutes from submitted_at
  Step 7 Slack:   owner_email and the handoff fields
  Run conditions: steps 3 to 5 skip customers and open deals;
  steps 4 and 5 run only when should_reply is true.
Map them in the Clay UI${WF_URL:+ at $WF_URL}, or per node with:
  clay workflows nodes get $WF_ID <nodeId>
  clay workflows nodes update $WF_ID <nodeId> --input node.json
graph.json in this folder shows the current nodes and edges.

EOF
read -r -p "Press Enter when inputs are mapped. "

echo "11/11 Validate, test once, publish"
clay workflows graph validate "$WF_ID"
clay workflows diagram "$WF_ID"

if jq -e 'all(.[]; . != "")' <<< "$TEST_ROW" > /dev/null; then
  read -r -p "Run TEST_ROW once? It can spend Clay credits, send a real email, update your CRM, and post to Slack. [y/N] " answer
  if [[ "$answer" == [yY] ]]; then
    clay workflows runs test "$WF_ID" --inputs "$TEST_ROW" > test-run.json
    echo "Saved test-run.json. Check each step with: clay workflows runs steps $WF_ID <runId>"
  fi
else
  echo "TEST_ROW is empty, skipping the test run."
fi

read -r -p "Publish the workflow now? [y/N] " answer
if [[ "$answer" == [yY] ]]; then
  clay workflows publish "$WF_ID" --name v1
fi

echo "Workflow $WF_ID, webhook trigger $TRG_ID. All responses are saved in $(pwd)."
~~~

### Sample form fill payload: sample-form-fill.json

~~~json
{
  "email": "jane@example.com",
  "first_name": "Jane",
  "last_name": "Doe",
  "company": "Example Co",
  "form_name": "Request a demo",
  "message": "We want to route inbound leads to 12 reps by territory.",
  "submitted_at": "2026-10-17T23:42:00Z",
  "booking_link": "https://meetings.example.com/sales-team"
}
~~~

### Send it a form fill

After publishing, read the webhook URL and send the sample payload once. Each POST is one run.

- Load the saved ids (run inside clay-speed-to-lead/): `WF_ID=$(jq -r '.id' workflow.json); TRG_ID=$(jq -r '.id' trigger.json)`
- Read the webhook URL (argument order inferred, see the verify notes): `WEBHOOK_URL=$(clay workflows triggers get "$WF_ID" "$TRG_ID" | jq -r '.webhookUrl')`
- Send the sample payload (needs a published workflow, one run per request): `curl -sS -X POST "$WEBHOOK_URL" -H 'Content-Type: application/json' --data @sample-form-fill.json`
- List runs: `clay workflows runs list "$WF_ID"`
- Inspect one run: `clay workflows runs get "$WF_ID" <runId>`
- See each step's output for that run: `clay workflows runs steps "$WF_ID" <runId>`

### Verify in the workspace

- The webhook trigger shape comes from the CLI help, but the help does not say how "triggers get" orders its arguments. If the run commands fail, copy the webhook URL from the Clay UI instead.
- The help documents "nodes create" with nodeType and name only. If it rejects actionPackageId and actionKey, create the node with a name only and pick the action in the Clay UI.
- The reference says action steps take actionPackageId and actionKey but shows only a utility example for "nodes insert", so the placement of those fields inside "step" is inferred.
- Only "workflows create" documents an id field in its response. The script reads .id from trigger and node responses too and stops if it is missing.
- Package ids and action keys differ by workspace and depend on which integrations you have connected. Copy them from your catalog; this page does not guess them.
- Step input mapping is dynamic per workspace. The CLI path is "nodes get" then "nodes update", where incomingEdges is a full replacement. The argument order for "nodes get" is inferred from "nodes update".
- The help does not say whether step 1 connects to the trigger automatically. "clay workflows graph validate" reports a disconnected graph.
- Reply minutes is submitted_at to sent_at. Compute it with a formula in Clay or a calculated field in the CRM, in the same unit as your baseline.
- "runs test" without --live is assumed to run the draft. The reference lists --live without describing it.
- Workflow runs can spend Clay credits and write to connected tools. Exit code 3 means re-run clay login, and exit code 4 means you are rate limited (wait for details.retryAfter).

## Guardrails

- Work only in the user's own accounts and workspaces, with their credentials.
- Confirm with the user before any step that writes to a CRM, sends email or messages, spends credits, or moves money.
- Treat every hours, cost, and threshold figure as a starting assumption and replace it with the user's numbers.
- Do not invent data, prices, or results. If a vendor price matters, check the vendor's current pricing page.

## Source

https://gtmplaybooks.io/playbooks/inbound-speed-to-lead-after-hours/
