Dialpad Skill
Send SMS and make voice calls via the Dialpad API.
When to Use
Native tools: submit_draft (registered by this skill's dialpad-draft-callback plugin). Owned entrypoints: list_sms_thread.py, list_sms_inbox.py, list_call_history.py, lookup_contact.py, poll_voicemails.py.
Use this skill to:
- Send SMS messages (individual or batch)
- Make voice calls (with TTS or custom voices)
- Manage contacts and organization settings
- Inspect SMS and call history through operator tooling when needed
Available Phone Numbers
| Number | Purpose | Format |
|---|---|---|
| (415) 555-0140 | Sales Team | Default for sales context |
| (415) 555-0100 | Work/Personal | Default for work context |
| (415) 555-0141 | Support SMS Only | SMS only (no voice) |
Quick Start
Send SMS (explicit sender recommended):
bin/send_sms.py --to "+14155551234" --from "+14155550140" --message 'Hello from OpenClaw!'
Send an operator-approved SMS and close a shown approval draft (agent direct path):
bin/send_sms.py --to "+14155551234" --from "+14155550140" --message 'Exact approved text' --resolve-draft-id smsdraft_abc123 --approval-actor-id "telegram-user-123" --approval-actor-username "operator" --json
Create/approve an SMS draft (human approval path):
bin/create_sms_draft.py --thread-key "manual:thread" --to "+14155551234" --from "+14155550140" --message 'Exact draft text' --json
bin/approve_sms_draft.py smsdraft_abc123 --actor-id "telegram-user-123" --actor-username "operator" --approval-token "$DIALPAD_SMS_APPROVAL_TOKEN" --json
Group Intro (mirrored fallback):
bin/send_group_intro.py --prospect "+14155550111" --reference "+14155559999" --confirm-share --from "+14155550100"
Make Call (TTS):
bin/make_call.py --to "+14155551234" --text "This is a call from the agent."
List Recent Calls:
bin/list_calls.py --today --limit 20
bin/list_calls.py --hours 6 --missed --json
Get Call Transcript:
bin/get_call_transcript.py --call-id "call_123" --json
bin/get_call_transcript.py --last --with "+14155551234" --json
Check SMS Thread History:
bin/list_sms_thread.py --phone "+14155551234" --json
bin/list_sms_inbox.py --limit 20 --json
When DIALPAD_LOG_URL is configured, SMS thread/inbox reads and call-list
reads use the authenticated theshop interaction log by default. Use
bin/list_calls.py --live only for an explicit live Dialpad query and
--local for the legacy local calls database. After a successful
bin/send_sms.py, the exact outbound observation is recorded through the
shared log or queued in the record-only DIALPAD_LOG_OUTBOX; replaying that
outbox never sends SMS or places calls.
Sync Direct Dialpad SMS Sends:
bin/sync_sms_export.py --start-date 2026-05-13 --end-date 2026-05-13 --json
Create Contact:
bin/create_contact.py --first-name "Jane" --last-name "Doe" --phone "+14155550123" --email "jane@example.com"
Update Contact:
bin/update_contact.py --id "contact_123" --phone "+14155550123" --job-title "VP"
Key Rules
- Destination policy: Outbound SMS and calls support NANP (
+1) destinations only, including all NANP territories. Non-NANP international destinations are rejected before any Dialpad API request. Always use E.164 format (e.g.,+14155551234); SMS country inference normalizes ten-digit NANP input to explicit+1E.164. The generatedmessage schedules.send_nowcommand is disabled because stored recipients cannot be validated locally; schedule updates must repeat phone recipients through--dataas a JSON array; meeting updates must setcall_outthrough--dataas a JSON boolean; and generated meeting create/update commands cannot enable call-out. - Escaping: Use single quotes for inline
--messagevalues containing$to prevent shell expansion (e.g.,'Price is $10'). - Safer message input: Prefer
--message-fileor--message-stdinfor pricing text, multi-line copy, or anything shell-sensitive. - Supported agent interface: use
bin/*.pywrappers for normal work. They are the stable command contract for agents. - Operator-only surfaces:
generated/dialpadandscripts/*are for manual troubleshooting, storage inspection, or operational maintenance, not normal agent task execution. - Auth canonical source:
DIALPAD_API_KEYis canonical.DIALPAD_TOKENis only needed for manual generated CLI troubleshooting (bridge command in Setup below). - SMS sender safety:
--fromand--profile work|salesare supported. Prefer explicit--fromfor deterministic routing.--profilemaps to configured env vars:- work:
DIALPAD_PROFILE_WORK_FROM - sales:
DIALPAD_PROFILE_SALES_FROM
- work:
- default fallback order:
DIALPAD_DEFAULT_FROM_NUMBER, thenDIALPAD_DEFAULT_PROFILE --allow-profile-mismatchpermits explicit/profile mismatches when intentional--dry-runprints sender resolution and the exact message/request preview without an API call
- Group intro:
bin/send_group_intro.pymirrors intro messages as two one-to-one SMS sends (mirrored_fallback) because true group threads are unsupported via this wrapper. - Call history:
bin/list_calls.pyis the supported call-history command for agents (supporting--today,--hours,--missed, and--localfor offline store queries).bin/list_call_history.pyprovides dedicated read-only local store queries with--phone,--direction,--min-duration, and--transcript-onlyfilters. Use--jsonwhen downstream automation needs a deterministic response envelope. - Call transcripts:
bin/get_call_transcript.pyis the supported transcript retrieval command for agents. It supports--localto retrieve transcripts deterministically from the local SQLite store without hitting the live Dialpad API. When hitting the live API, it uses Dialpad's plural transcript endpoints (GET /api/v2/transcripts/{call_id}and optional review URLGET /api/v2/transcripts/{call_id}/url); singular/api/v2/transcript/{id}is not the supported endpoint. It is transcript-only; AI recap, CRM enrichment, and follow-up drafting are intentionally separate work. - Call & SMS local stores: Incoming webhooks persist events in local append-only SQLite databases (
DIALPAD_SMS_DBdefaulting to/home/art/niemand/logs/sms.db, andDIALPAD_CALLS_DBdefaulting to/home/art/niemand/logs/calls.db). Before saying a contact was not messaged or called, inspect local history first. For SMS, runbin/list_sms_thread.py --phone PHONE --jsonand checkhas_outbound/outbound_count. For calls, runbin/list_call_history.py --phone PHONE --json. - SMS export sync:
bin/sync_sms_export.pyimports Dialpad Stats text export metadata into local SQLite for direct Dialpad UI sends and other out-of-band messages. It skips existing message IDs to preserve webhook-captured text. - Create/Update Contact Behavior:
bin/create_contact.pyupserts shared/local contacts by phone/email match (or forces create with--allow-duplicate).bin/update_contact.pyupdates by--idwith partial fields. - Current-turn verification: "Already sent" and "Already updated" are only valid after a fresh current-turn tool result, not from stale session memory. If the current turn has not verified the action yet, say that plainly and run the tool now.
- SMS receipt ledger: Successful sends through
bin/send_sms.py, both legs ofbin/send_group_intro.py, and approval-lane sends append authoritative JSONL receipt evidence toDIALPAD_SMS_RECEIPT_LEDGER, defaulting to/data/.openclaw/state/dialpad/sms-receipts.jsonl. Failed sends and dry runs do not write receipts. - Receipt phrasing: When confirming an SMS send to an operator, always include
To: <number>in the receipt text so the downstream guard can bind the claim to the ledger recipient. - Delivery-status webhooks:
bin/create_sms_webhook.py createprovisions the SMS subscription withstatus: true. Authenticated sparse delivery events update only the matchingsms.dbrow by provider message ID and never enter inbound dedupe, OpenClaw hooks, Telegram, or approval-draft fan-out. Receipt events require a parseable providerevent_timestamp; an empty oracceptedresult is required alongsidedelivered, while unknown, stale, and contradictory states fail closed. - Identity guardrail: For first-contact work, soft signals like first name, area code, industry, or job title are not enough to merge or update a contact. Keep uncertain identity
draft-onlyfor autonomous sends and contact mutation — let the CRM layer prove the match before auto-sending or mutating anything. However, explicit operator approval (rule 20) lifts the draft-only send restriction: the whole point of the approval path is that the human makes the identity judgment call. Do not refuse to send a draft that an operator has explicitly approved in the current turn. - Inbound context guardrail: Webhook
inboundContextbriefs explain identity evidence, recent SMS/call continuity, source status, and draft basis. Low-confidence Sales SMS and missed calls may get generic approval drafts, but not personalized claims. Known contacts only get context-aware, CRM-aware, meeting-aware, or availability-aware approval drafts when identity confidence is high and relevant continuity is no older than 14 days; stale or degraded context stays brief-only. Calendar lookup is only for obvious meeting logistics and demo-prospect availability requests, not every inbound SMS. If availability cannot be verified, do not turn the CRM generic fallback into a scheduling reply. Demo-context missed calls may include operator-only prior-comms provenance from SMS/Gmail counts; raw comms bodies must not be copied into customer-facing draft text. If a model is used for final wording, it must receive compact tool-call facts plus the deterministic fallback and fail closed to that fallback on unsafe or unsupported output. - Inbound automation guardrail: Dialpad inbound hooks may create SMS approval drafts, but they must not send customer SMS directly. Use
bin/approve_sms_draft.pyonly with a real human actor id and an operator-onlyDIALPAD_SMS_APPROVAL_TOKEN; agent/bot actors are rejected by the trusted approval ledger. - Operator-approved agent sends: Agents may send SMS with
bin/send_sms.pyafter explicit current-turn operator approval. When resolving a shown approval draft, pass--resolve-draft-idplus--approval-actor-id/--approval-actor-usernameso the draft closes as an auditedagent_direct_send; that actor context is agent-asserted unless it came through a trusted Telegram/shell approval surface. Risky drafts still require the existing two-step approval state before--confirm-riskcan send. - Opt-out guardrail: Explicit opt-out language is a hard stop. Do not create override drafts or send follow-ups on those threads unless a human operator handles the conversation outside automation.
- Operator notification ownership: One inbound SMS or missed call should create one operator-visible Telegram message per target by default. When the local Dialpad Telegram approval card and OpenClaw hook route point at the same Telegram destination, the local card owns visibility and the hook is context-only with
deliver=false. UseDIALPAD_ALLOW_DUPLICATE_OPERATOR_DELIVERY=1only for intentional same-target fanout. - Generated CLI command structure & safe discovery: The underlying generated CLI uses
<resource> <resource>.<method>naming (e.g.dialpad sms sms.send,dialpad users users.get,dialpad call call.create_call). Avoid probing guessed subcommands such asusers list(which does not exist in this generated CLI). When inspecting commands, usegenerated/dialpad <resource> --helpor rely onbin/*.pywrappers. - Send SMS managed environment & direct API fallback:
bin/send_sms.pyexecutes generated Dialpad CLI calls through a managed environment (automatically discoveringuvacross PATH and standard locations like~/.cargo/bin/uvwithclickandrequests). If the generated CLI or its Python runtime dependencies are missing,bin/send_sms.pyautomatically falls back to direct Dialpad API execution, ensuring approved outbound SMS messages never remain unsent due to environment gaps.
Reference Documentation
references/api-reference.md— Wrapper behavior, operator CLI reference, Webhooksreferences/openclaw-integration.md— End-to-end setup guide for wiring this repo's webhook server to OpenClaw with human approval defaultsreferences/sms-storage.md— SQLite commands, FTS5 search, legacy storagereferences/voice-options.md— List of available TTS voices (Budget & Premium)references/architecture.md— System architecture, wrappers, and CLI generationTHEORY.MD— Design rationale: shell-corruption invariant, 401 classification, inbound-policy-once model
Operational Tools
Use these only for manual operator workflows, storage inspection, and maintenance:
python3 scripts/sms_sqlite.py list
python3 scripts/webhook_server.py
Setup
Required environment variable:
export DIALPAD_API_KEY="your_key"
Operator auth bridge for manual generated CLI troubleshooting:
export DIALPAD_TOKEN="${DIALPAD_TOKEN:-$DIALPAD_API_KEY}"
Optional:
export ELEVENLABS_API_KEY="your_key"
export DIALPAD_USER_MAP='{"+14155550100": "5765607478525952"}'
export DIALPAD_PROFILE_WORK_FROM="+14155550100"
export DIALPAD_PROFILE_SALES_FROM="+14155550140"
export DIALPAD_DEFAULT_PROFILE="work"
export DIALPAD_DEFAULT_FROM_NUMBER="+14155550140"
export DIALPAD_SMS_RECEIPT_LEDGER="/data/.openclaw/state/dialpad/sms-receipts.jsonl"
export DIALPAD_DRAFT_CALLBACK_URL="http://host.docker.internal:8888/internal/draft-callback"
For merged agent-draft flow, keep DIALPAD_DRAFT_CALLBACK_URL aligned with the
OpenClaw dialpad-draft-callback plugin callbackUrl config. The default
host.docker.internal URL is for gateway-container to host-webhook delivery.