Conversational multi-step flow to create a new contact in the CRM
A conversational contact creation flow. NOT a form — gather info step by step, accept partial/multiple answers, create incrementally.
source /opt/openclaw.env
Reference: skills/crm-contacts/SKILL.md for all CRUD operations.
Extract as much as possible from the CRM context sent by the frontend.
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/command_center_inbox?id=eq.{INBOX_ID}&select=id,from_name,from_email,subject,body_text,to_recipients,cc_recipients,date" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
Extract from the result:
from_name (split into first_name / last_name)from_emailsource /opt/openclaw.env
# Search inbox for recent WhatsApp messages from this chat
curl -sS "${SUPABASE_URL}/rest/v1/command_center_inbox?type=eq.whatsapp&chat_name=ilike.%25{CHAT_NAME}%25&order=date.desc&limit=1&select=id,chat_name,contact_number,from_name,body_text,date" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
Extract: name from chat_name or from_name, phone from contact_number.
Use it directly as first_name/last_name.
Run ALL applicable checks before proceeding. Do NOT skip any tier.
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contact_emails?email=eq.{EMAIL}&select=email_id,contact_id,email,contacts(contact_id,first_name,last_name,category)" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
If match → "This email already belongs to {name} ({category}). Is this who you mean?"
source /opt/openclaw.env
# Exact: first_name + last_name
curl -sS "${SUPABASE_URL}/rest/v1/contacts?first_name=ilike.{FIRST}&last_name=ilike.{LAST}&select=contact_id,first_name,last_name,category&limit=5" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
# Reversed: first_name = last, last_name = first
curl -sS "${SUPABASE_URL}/rest/v1/contacts?first_name=ilike.{LAST}&last_name=ilike.{FIRST}&select=contact_id,first_name,last_name,category&limit=5" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contacts?last_name=ilike.{LAST}&first_name=ilike.{FIRST_INITIAL}%25&select=contact_id,first_name,last_name,category&limit=5" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contacts?last_name=ilike.{LAST}&select=contact_id,first_name,last_name,category&limit=10" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
Use these as related contacts — show them for context: "Note: {name} ({category}) shares the surname."
Only if email domain is NOT in: gmail.com, yahoo.com, hotmail.com, outlook.com, icloud.com, live.com, me.com, protonmail.com, mail.com
source /opt/openclaw.env
# Find contacts with same first name who have an email at the same domain
curl -sS "${SUPABASE_URL}/rest/v1/contact_emails?email=ilike.%25@{DOMAIN}&select=contact_id,email,contacts(contact_id,first_name,last_name,category)" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contact_emails?email=ilike.%25@{DOMAIN}&select=contact_id,email,contacts(contact_id,first_name,last_name,category)&limit=10" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contacts_hold?or=(first_name.ilike.%25{FIRST}%25,last_name.ilike.%25{LAST}%25)&select=*" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
If exact match (Tier 1-2): "Found {name} ({category}) — is this the same person? If yes, I won't create a duplicate."
If partial match (Tier 3-5): "Found possible matches: {list}. Any of these the same person?"
If related contacts (Tier 4): "Note: {name} ({category}) shares the surname — could be related."
Wait for Simone's answer before proceeding.
After duplicate check clears, present what you gathered:
Here's what I have:
- Name: {first_name} {last_name}
- Email: {email} (if available)
- Phone: {phone} (if available)
- Source: {Email/WhatsApp}
- Related: {related contacts if any}
What category? (Founder, Professional Investor, Manager, Friend and Family, Advisor, Supplier, Media, Student, Other)
Ask ONE thing at a time. Priority order:
Founder, Professional Investor, Manager, Friend and Family, Advisor, Supplier, Media, Student, Otheremployee, founder, advisor, manager, investor, otherMonthly, Quarterly, Twice per Year, Once per Year, Weekly, Do not keep in touchwhatsapp standard, email standard, no wisheswhatsapp standard, email standard, no wishesFollow skills/crm-contacts/SKILL.md Section 13 workflow. Always source /opt/openclaw.env before each curl.
source /opt/openclaw.env
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contacts" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{
"first_name": "{FIRST}",
"last_name": "{LAST}",
"category": "{CATEGORY}",
"job_role": "{JOB_ROLE}",
"description": "{DESCRIPTION}",
"score": {SCORE},
"linkedin": "{LINKEDIN}",
"show_missing": true,
"created_by": "LLM"
}'
Save contact_id from response.
source /opt/openclaw.env
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contact_emails" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"contact_id": "{CONTACT_ID}", "email": "{EMAIL_LOWERCASE}", "is_primary": true}'
source /opt/openclaw.env
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contact_mobiles" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"contact_id": "{CONTACT_ID}", "mobile": "{PHONE}", "type": "personal", "is_primary": true}'
Search company first, create if not found:
source /opt/openclaw.env
# Search
curl -sS "${SUPABASE_URL}/rest/v1/companies?name=ilike.%25{COMPANY_NAME}%25&select=company_id,name,category" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
# Link
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contact_companies" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"contact_id": "{CONTACT_ID}", "company_id": "{COMPANY_ID}", "relationship": "{RELATIONSHIP}", "is_primary": true}'
source /opt/openclaw.env
# Search city
curl -sS "${SUPABASE_URL}/rest/v1/cities?name=ilike.%25{CITY}%25&select=city_id,name" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
# Create city if not found
curl -sS -X POST "${SUPABASE_URL}/rest/v1/cities" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"name": "{CITY}"}'
# Link
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contact_cities" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"contact_id": "{CONTACT_ID}", "city_id": "{CITY_ID}"}'
source /opt/openclaw.env
# Search tag
curl -sS "${SUPABASE_URL}/rest/v1/tags?name=ilike.%25{TAG}%25&select=tag_id,name" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
# Link (tag must exist — search first, create only if user confirms new tag)
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contact_tags" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{"contact_id": "{CONTACT_ID}", "tag_id": "{TAG_ID}"}'
source /opt/openclaw.env
curl -sS -X POST "${SUPABASE_URL}/rest/v1/keep_in_touch" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation,resolution=merge-duplicates" \
-d '{
"contact_id": "{CONTACT_ID}",
"frequency": "{FREQUENCY}",
"christmas": "{CHRISTMAS}",
"easter": "{EASTER}"
}'
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contacts?contact_id=eq.{CONTACT_ID}&select=*,contact_emails(*),contact_mobiles(*),contact_cities(city_id,cities(city_id,name)),contact_tags(entry_id,tags(tag_id,name)),contact_companies(contact_companies_id,relationship,is_primary,companies(company_id,name,category)),keep_in_touch(*)" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
source /opt/openclaw.env
curl -sS "${SUPABASE_URL}/rest/v1/contact_completeness?contact_id=eq.{CONTACT_ID}&select=completeness_score,email_count,mobile_count,company_count,city_count,tag_count,kit_frequency,christmas,easter" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY"
Present a summary:
Created **{first_name} {last_name}** ({category}) — completeness {score}%
✓ Name, ✓ Category, ✓ Email, ✓ Company (Acme Inc)
✗ Missing: description, LinkedIn, photo, birthday, tags
List what's present (✓) and what's missing (✗) based on the 17-point formula:
After reporting completeness, if score < 80%, offer:
"Want me to activate Barbara to enrich this contact? She can look up LinkedIn, job title, photo, city, and company details."
If Simone says yes:
source /opt/openclaw.env
# Determine missing dimensions from completeness query
# Possible dimensions: completeness, photo, note, company, company_complete
curl -sS -X POST "${SUPABASE_URL}/rest/v1/contacts_clarissa_processing" \
-H "apikey: $SUPABASE_KEY" -H "Authorization: Bearer $SUPABASE_KEY" \
-H "Content-Type: application/json" -H "Prefer: return=representation" \
-d '{
"contact_id": "{CONTACT_ID}",
"bucket": "new_contact",
"missing_dimensions": ["completeness", "photo", "company_complete"],
"missing_details": {
"missing_fields": ["linkedin", "description", "photo", "birthday"],
"source": "create-contact"
}
}'
Respond: "Queued for Barbara — she'll enrich LinkedIn, photo, and company details."
cat >> ops-log.md <<LOGEOF
- Contact: created "{first_name} {last_name}" | {category} | completeness:{score}% | id:{CONTACT_ID}
LOGEOF