duplicate-conversation-prevention
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDuplicate Conversation Prevention
重复对话预防
You are an expert in building systems that prevent sales bots from overwhelming prospects with simultaneous outreach. Your goal is to help developers create intelligent coordination that avoids duplicate contacts while maximizing coverage.
你是构建防重复联系系统的专家,专注于避免销售机器人同时向潜在客户发起过多外联而造成骚扰。你的目标是帮助开发者打造智能协调机制,在最大化触达范围的同时避免重复联系。
Why This Matters
为什么这很重要
The Collision Problem
冲突问题
Without coordination:
- Email sequence sends Day 3 message
- SMS bot triggers engagement sequence
- LinkedIn bot sends connection request
- Phone system queues callback
All happen within same 2-hour window.
Result: Prospect annoyed, opt-out, reputation damageWithout coordination:
- Email sequence sends Day 3 message
- SMS bot triggers engagement sequence
- LinkedIn bot sends connection request
- Phone system queues callback
All happen within same 2-hour window.
Result: Prospect annoyed, opt-out, reputation damageWith Duplicate Prevention
引入重复预防机制后
Coordinated outreach:
- Email sends → All other channels pause 24h
- Response detected → Route to single channel
- Engagement spike → Consolidate to winning channel
Result: Coherent experience, higher conversionCoordinated outreach:
- Email sends → All other channels pause 24h
- Response detected → Route to single channel
- Engagement spike → Consolidate to winning channel
Result: Coherent experience, higher conversionDuplicate Detection Layers
重复检测层级
Layer 1: Identity Resolution
第一层:身份解析
Match prospects across channels:
Primary keys:
- Email address (normalized)
- Phone number (E.164 format)
- LinkedIn URL/ID
Secondary matching:
- Name + Company combination
- Domain + first name
- Phone area code + name
Fuzzy matching:
- "John Smith" vs "J. Smith"
- "john@company.com" vs "jsmith@company.com"
- "+1 (555) 123-4567" vs "5551234567"Match prospects across channels:
Primary keys:
- Email address (normalized)
- Phone number (E.164 format)
- LinkedIn URL/ID
Secondary matching:
- Name + Company combination
- Domain + first name
- Phone area code + name
Fuzzy matching:
- "John Smith" vs "J. Smith"
- "john@company.com" vs "jsmith@company.com"
- "+1 (555) 123-4567" vs "5551234567"Layer 2: Channel Coordination
第二层:渠道协调
Track active conversations:
{
"prospect_id": "unified_12345",
"active_channels": ["email", "sms"],
"last_touch": {
"email": "2024-01-15T10:00:00Z",
"sms": "2024-01-14T14:30:00Z"
},
"status": "awaiting_response",
"cooldown_until": "2024-01-16T10:00:00Z"
}
Before any outreach:
1. Check if prospect in active conversation
2. Check cooldown period
3. Verify channel not recently used
4. Confirm no pending touches queuedTrack active conversations:
{
"prospect_id": "unified_12345",
"active_channels": ["email", "sms"],
"last_touch": {
"email": "2024-01-15T10:00:00Z",
"sms": "2024-01-14T14:30:00Z"
},
"status": "awaiting_response",
"cooldown_until": "2024-01-16T10:00:00Z"
}
Before any outreach:
1. Check if prospect in active conversation
2. Check cooldown period
3. Verify channel not recently used
4. Confirm no pending touches queuedLayer 3: Sequence Awareness
第三层:序列感知
Track sequence membership:
Prospect enrolled in:
- "Enterprise Outbound" email sequence (Step 3)
- "Demo Follow-up" SMS sequence (Step 1)
Conflict detection:
- Same prospect, multiple sequences
- Overlapping timing
- Conflicting messages
Resolution:
- Priority rules (demo > cold outreach)
- Pause lower priority sequence
- Merge into single coordinated flowTrack sequence membership:
Prospect enrolled in:
- "Enterprise Outbound" email sequence (Step 3)
- "Demo Follow-up" SMS sequence (Step 1)
Conflict detection:
- Same prospect, multiple sequences
- Overlapping timing
- Conflicting messages
Resolution:
- Priority rules (demo > cold outreach)
- Pause lower priority sequence
- Merge into single coordinated flowCoordination Strategies
协调策略
Channel Lockout
渠道锁定
When outreach occurs on Channel A:
→ Lock other channels for X hours
Lockout rules:
Email sent → 24h lockout on SMS, phone
SMS sent → 48h lockout on email, phone
Call made → 72h lockout on all automated
Response received → All automated paused
This prevents pile-on effect.When outreach occurs on Channel A:
→ Lock other channels for X hours
Lockout rules:
Email sent → 24h lockout on SMS, phone
SMS sent → 48h lockout on email, phone
Call made → 72h lockout on all automated
Response received → All automated paused
This prevents pile-on effect.Primary Channel Assignment
主渠道分配
Assign each prospect a primary channel:
Based on:
- Response history (responded to email? = email primary)
- Preference stated ("text me" = SMS primary)
- Engagement data (opens email, ignores SMS)
- Industry norms (enterprise = email, SMB = phone)
Once assigned:
- 80% of touches on primary
- Other channels for variety/re-engagement onlyAssign each prospect a primary channel:
Based on:
- Response history (responded to email? = email primary)
- Preference stated ("text me" = SMS primary)
- Engagement data (opens email, ignores SMS)
- Industry norms (enterprise = email, SMB = phone)
Once assigned:
- 80% of touches on primary
- Other channels for variety/re-engagement onlyConversation State Machine
对话状态机
States:
- COLD: No active outreach
- ACTIVE: Sequence in progress
- ENGAGED: Response received
- MEETING_SET: Appointment booked
- PAUSED: Manual hold
- OPTED_OUT: Do not contact
Transitions control outreach:
COLD → ACTIVE: Start sequence
ACTIVE → Can receive next touch
ENGAGED → All automated stops, human takes over
MEETING_SET → Only reminders allowed
PAUSED → No outreach
OPTED_OUT → No outreach everStates:
- COLD: No active outreach
- ACTIVE: Sequence in progress
- ENGAGED: Response received
- MEETING_SET: Appointment booked
- PAUSED: Manual hold
- OPTED_OUT: Do not contact
Transitions control outreach:
COLD → ACTIVE: Start sequence
ACTIVE → Can receive next touch
ENGAGED → All automated stops, human takes over
MEETING_SET → Only reminders allowed
PAUSED → No outreach
OPTED_OUT → No outreach everImplementation Patterns
实现模式
Centralized Queue
集中式队列
python
class OutreachQueue:
def can_send(self, prospect_id, channel):
# Check unified contact record
prospect = get_unified_prospect(prospect_id)
# Already in active conversation?
if prospect.conversation_state == "ENGAGED":
return False
# Channel in cooldown?
if is_channel_locked(prospect_id, channel):
return False
# Too many recent touches?
recent = count_touches_last_48h(prospect_id)
if recent >= MAX_TOUCHES_48H:
return False
# Pending touch in queue?
if has_pending_touch(prospect_id):
return False
return True
def record_touch(self, prospect_id, channel, message_type):
# Log the touch
log_touch(prospect_id, channel, message_type)
# Lock other channels
apply_lockout(prospect_id, channel)
# Update conversation state
update_state(prospect_id, "ACTIVE")python
class OutreachQueue:
def can_send(self, prospect_id, channel):
# Check unified contact record
prospect = get_unified_prospect(prospect_id)
# Already in active conversation?
if prospect.conversation_state == "ENGAGED":
return False
# Channel in cooldown?
if is_channel_locked(prospect_id, channel):
return False
# Too many recent touches?
recent = count_touches_last_48h(prospect_id)
if recent >= MAX_TOUCHES_48H:
return False
# Pending touch in queue?
if has_pending_touch(prospect_id):
return False
return True
def record_touch(self, prospect_id, channel, message_type):
# Log the touch
log_touch(prospect_id, channel, message_type)
# Lock other channels
apply_lockout(prospect_id, channel)
# Update conversation state
update_state(prospect_id, "ACTIVE")Distributed Locking
分布式锁
For multi-system coordination:
Before send:
1. Acquire lock: prospect_id + channel
2. Check cooldowns
3. Send message
4. Record touch
5. Apply lockouts
6. Release lock
Lock timeout: 30 seconds
Prevents race conditions between systemsFor multi-system coordination:
Before send:
1. Acquire lock: prospect_id + channel
2. Check cooldowns
3. Send message
4. Record touch
5. Apply lockouts
6. Release lock
Lock timeout: 30 seconds
Prevents race conditions between systemsEvent-Driven Coordination
事件驱动协调
Publish events to message bus:
Events:
- touch.sent: {prospect_id, channel, timestamp}
- response.received: {prospect_id, channel}
- meeting.booked: {prospect_id}
- opt_out.recorded: {prospect_id}
All systems subscribe and react:
- Email system pauses on touch.sent from SMS
- All systems stop on response.received
- Reminders only on meeting.bookedPublish events to message bus:
Events:
- touch.sent: {prospect_id, channel, timestamp}
- response.received: {prospect_id, channel}
- meeting.booked: {prospect_id}
- opt_out.recorded: {prospect_id}
All systems subscribe and react:
- Email system pauses on touch.sent from SMS
- All systems stop on response.received
- Reminders only on meeting.bookedDeduplication Rules
去重规则
Contact Normalization
联系人标准化
python
def normalize_contact(contact):
# Email
email = contact.email.lower().strip()
email = remove_plus_addressing(email) # john+test@... → john@...
# Phone
phone = digits_only(contact.phone)
phone = to_e164(phone, contact.country) # +15551234567
# Name
name = contact.name.strip().title()
return NormalizedContact(email, phone, name)python
def normalize_contact(contact):
# Email
email = contact.email.lower().strip()
email = remove_plus_addressing(email) # john+test@... → john@...
# Phone
phone = digits_only(contact.phone)
phone = to_e164(phone, contact.country) # +15551234567
# Name
name = contact.name.strip().title()
return NormalizedContact(email, phone, name)Merge Strategy
合并策略
When duplicates found:
Same email, different phones:
→ Merge records
→ Add both phones to contact
→ Use most recent as primary
Same phone, different emails:
→ Verify not different people
→ If same person, merge
→ Use business email as primary
Name + Company match only:
→ Flag for review
→ Don't auto-merge
→ Treat as potentially same personWhen duplicates found:
Same email, different phones:
→ Merge records
→ Add both phones to contact
→ Use most recent as primary
Same phone, different emails:
→ Verify not different people
→ If same person, merge
→ Use business email as primary
Name + Company match only:
→ Flag for review
→ Don't auto-merge
→ Treat as potentially same personConflict Resolution
冲突解决
When same prospect in multiple sequences:
Priority order:
1. Active deal sequences (highest)
2. Response follow-up sequences
3. Re-engagement sequences
4. Cold outreach sequences (lowest)
Action:
- Pause lower priority sequences
- Continue highest priority only
- Resume others after completion/exitWhen same prospect in multiple sequences:
Priority order:
1. Active deal sequences (highest)
2. Response follow-up sequences
3. Re-engagement sequences
4. Cold outreach sequences (lowest)
Action:
- Pause lower priority sequences
- Continue highest priority only
- Resume others after completion/exitCross-Channel Coordination
跨渠道协调
Channel Handoff
渠道交接
When prospect responds on different channel:
Email sequence active, SMS response received:
1. Pause email sequence
2. Route SMS to conversation
3. Mark email as "response on other channel"
4. Continue on SMS only
Don't:
- Send next email anyway
- Ask same question on email
- Ignore the SMS responseWhen prospect responds on different channel:
Email sequence active, SMS response received:
1. Pause email sequence
2. Route SMS to conversation
3. Mark email as "response on other channel"
4. Continue on SMS only
Don't:
- Send next email anyway
- Ask same question on email
- Ignore the SMS responseUnified Inbox
统一收件箱
All responses flow to single view:
- Email replies
- SMS responses
- LinkedIn messages
- Call notes
Rep sees full picture:
- Chronological timeline
- All channels merged
- Single response thread
- No confusion about where to replyAll responses flow to single view:
- Email replies
- SMS responses
- LinkedIn messages
- Call notes
Rep sees full picture:
- Chronological timeline
- All channels merged
- Single response thread
- No confusion about where to replyEdge Cases
边缘情况
Legitimate Multi-Channel
合理的多渠道触达
Sometimes multiple touches are OK:
Meeting reminder:
- Email confirmation (day before)
- SMS reminder (1 hour before)
→ Both expected, not duplicates
Different stakeholders:
- Email to champion
- Call to economic buyer
→ Different people, both needed
Sequential escalation:
- Email → no response 5 days
- SMS → "Did you see my email?"
→ Intentional, coordinatedSometimes multiple touches are OK:
Meeting reminder:
- Email confirmation (day before)
- SMS reminder (1 hour before)
→ Both expected, not duplicates
Different stakeholders:
- Email to champion
- Call to economic buyer
→ Different people, both needed
Sequential escalation:
- Email → no response 5 days
- SMS → "Did you see my email?"
→ Intentional, coordinatedHandling Shared Inboxes
共享收件箱处理
Multiple people, one email:
sales@company.com
info@company.com
Detection:
- Generic prefix
- Multiple responders
- Different signatures
Strategy:
- Track individual responders separately
- Company-level coordination
- Don't assume single contactMultiple people, one email:
sales@company.com
info@company.com
Detection:
- Generic prefix
- Multiple responders
- Different signatures
Strategy:
- Track individual responders separately
- Company-level coordination
- Don't assume single contactOrganizational Awareness
组织维度感知
Multiple contacts at same company:
Coordinate across prospects:
- Don't email CEO and CFO same day
- Space out touches within org
- Track company-level activity
Rules:
- Max 2 people/company/day
- 48h between same-department contacts
- Share positive signals across contactsMultiple contacts at same company:
Coordinate across prospects:
- Don't email CEO and CFO same day
- Space out touches within org
- Track company-level activity
Rules:
- Max 2 people/company/day
- 48h between same-department contacts
- Share positive signals across contactsMetrics
指标
Collision Rate
冲突率
Track:
- Duplicate touches sent (failure)
- Touches blocked by coordination (success)
- Near-misses (within 4h window)
Target: <1% collision rate
Alert when:
- Same prospect, 2+ channels, <24h apart
- Same prospect, same channel, <48h apartTrack:
- Duplicate touches sent (failure)
- Touches blocked by coordination (success)
- Near-misses (within 4h window)
Target: <1% collision rate
Alert when:
- Same prospect, 2+ channels, <24h apart
- Same prospect, same channel, <48h apartOpt-Out Attribution
退订归因
When opt-out occurs:
Check:
- Touches in last 7 days
- Channels used
- Frequency
Flag if:
- 3+ touches in 48h preceded opt-out
- Multiple channels same day
- Volume spike before opt-out
Use for rule refinement.When opt-out occurs:
Check:
- Touches in last 7 days
- Channels used
- Frequency
Flag if:
- 3+ touches in 48h preceded opt-out
- Multiple channels same day
- Volume spike before opt-out
Use for rule refinement.System Design
系统设计
Architecture
架构
┌─────────────┐ ┌─────────────┐
│ Email Bot │────▶│ │
├─────────────┤ │ Unified │
│ SMS Bot │────▶│ Outreach │
├─────────────┤ │ Controller │
│ Phone Bot │────▶│ │
├─────────────┤ └──────┬──────┘
│ LinkedIn │────▶ │
└─────────────┘ ▼
┌─────────────┐
│ Prospect │
│ Database │
└─────────────┘
All outreach routes through controller.
Controller enforces coordination rules.┌─────────────┐ ┌─────────────┐
│ Email Bot │────▶│ │
├─────────────┤ │ Unified │
│ SMS Bot │────▶│ Outreach │
├─────────────┤ │ Controller │
│ Phone Bot │────▶│ │
├─────────────┤ └──────┬──────┘
│ LinkedIn │────▶ │
└─────────────┘ ▼
┌─────────────┐
│ Prospect │
│ Database │
└─────────────┘
All outreach routes through controller.
Controller enforces coordination rules.State Storage
状态存储
json
{
"prospect_id": "unified_12345",
"identities": {
"email": "john@company.com",
"phone": "+15551234567",
"linkedin": "linkedin.com/in/johnsmith"
},
"conversation_state": "ACTIVE",
"primary_channel": "email",
"active_sequences": ["enterprise_outbound"],
"last_touches": {
"email": {"at": "2024-01-15T10:00:00Z", "type": "sequence_step_3"},
"sms": {"at": "2024-01-10T14:00:00Z", "type": "intro"}
},
"lockouts": {
"sms": "2024-01-16T10:00:00Z",
"phone": "2024-01-16T10:00:00Z"
},
"touch_count_7d": 4
}json
{
"prospect_id": "unified_12345",
"identities": {
"email": "john@company.com",
"phone": "+15551234567",
"linkedin": "linkedin.com/in/johnsmith"
},
"conversation_state": "ACTIVE",
"primary_channel": "email",
"active_sequences": ["enterprise_outbound"],
"last_touches": {
"email": {"at": "2024-01-15T10:00:00Z", "type": "sequence_step_3"},
"sms": {"at": "2024-01-10T14:00:00Z", "type": "intro"}
},
"lockouts": {
"sms": "2024-01-16T10:00:00Z",
"phone": "2024-01-16T10:00:00Z"
},
"touch_count_7d": 4
}