Match iMessage replies to the right CRM contact
A practical contact-matching design for Pipedrive and other CRMs: normalize addresses, resolve ambiguous matches, preserve suppression, and route replies to the right owner.
A phone number is a destination, not your customer database key
An iMessage integration should attach a reply to a known customer record using the business workspace, sending line, and verified conversation mapping. A matching display name is not enough. Neither is a phone number copied into two CRM records without a rule for resolving the duplicate.
Keep your CRM’s stable Person or Contact ID as the business reference. Store phone numbers and email addresses as attributes with provenance and current verification state. Your integration owns this mapping; the model answering a message should never choose a customer record by guessing from the text.
Normalize addresses without inventing identity
Use a maintained phone-number parser and known country context to produce a consistent international form. Simply removing punctuation and prepending a country code can corrupt an international number. Google’s libphonenumber documents parsing, formatting, and validation; validation of a number’s shape does not prove who owns it or permission to message it.
Preserve the original supplied value for controlled troubleshooting, alongside the normalized destination. For email addresses, use a documented normalization policy and avoid provider-specific guesses such as stripping dots or plus aliases globally. A phone address and an email address can belong to one person, but that association needs evidence from your application or a reviewed record.
Keep the mapping scoped and auditable
The following fields are a proposed adapter design, not a built-in CRM schema or a Miss Blue API payload. Separate current routing from the history of how the association changed. This makes an incorrect merge reversible without rewriting the original inbound events.
Swipe across the table to see every column.
| Record | Example key | Purpose |
|---|---|---|
| Customer | CRM company + Person/Contact ID | Prevents cross-account matching |
| Destination | Project + business number ID + normalized handle | Restricts the lookup to the intended business line |
| Conversation | Project + number ID + thread ID | Preserves a reviewed association when an event lacks an address |
| Opportunity | Company + Deal ID + workflow enrollment | Keeps two deals for one person from silently sharing follow-ups |
| Permission and suppression | Contact/destination + scope + evidence + effective time | Survives imports and prevents queued work from bypassing a block |
| Routing revision | Owner ID + mapping version + changed time | Makes reassignment and stale jobs detectable |
Suggested records owned by your CRM integration.
Match an incoming Miss Blue reply in a deliberate order
Verify the signed project webhook and store its envelope id before downstream effects. For message.received, the business line is data.number.id and the sender’s address is data.message.sender_handle. The from field is an opaque platform identifier, not a phone number. Enrichment can be missing, so handle null values explicitly.
First use a reviewed mapping for the scoped thread. Check any supplied sender address against it; a conflicting identity should stop automatic association. Otherwise search the exact normalized destination within the authorized workspace and line. Accept one unambiguous match. With zero or multiple candidates, queue the event for review and avoid automatically creating a sales reply.
When a known person has several open deals, select a deal only from a persisted workflow or explicit routing rule. “Most recently updated” is a fragile default: an unrelated deal edit can move a customer’s appointment question into a different pipeline.
Work through a duplicate-contact example
Suppose a fictional CRM contains Person 101, Jordan Lee, and Person 208, J. Lee, both with the same normalized phone number. A reply arrives on the sales line with no reviewed thread association. The correct automatic result is an ambiguous-match task, not a new Person 309 and not a message to whichever record appears first.
A teammate confirms that the records represent the same customer and chooses the surviving CRM record. Persist that decision, retain the old-to-new reference for historical events, and reassess the active deal and owner. Reprocess the saved event under its original deduplication key so the correction creates one follow-up activity.
If the number is shared by two people, do not merge them. Preserve the ambiguity and use an agreed verification step or human conversation to establish context. Technical normalization cannot resolve a shared household phone or a reassigned business number.
Make imports and owner changes preserve the block
A nightly CRM import should update the fields it owns without clearing a contact’s suppression state. Define the scope of a block and how it follows merged records and normalized addresses. When two records merge, preserve the restrictive state until an authorized person reviews the conflict.
Re-read current permission and routing immediately before dispatch. A queued job created yesterday must not send after today’s block or from an owner’s old number after reassignment. Miss Blue contact blocking and your adapter’s workflow state have different responsibilities; check both where applicable.
Pipedrive webhook v2 provides a unique meta.id and prior changed fields in previous. Treat an owner or stage event as a prompt to reload and evaluate current state, then persist the decision. Do not repeatedly enroll a deal because another field was edited.
Test the cases that produce wrong-customer messages
Use invented contacts in a sandbox and inspect both the association and the resulting action. A test passes when the right record is selected and no other record receives an update. Message delivery alone does not validate contact matching.
- The same phone appears in two CRM companies: no cross-company match.
- A known thread arrives without sender_handle: use only its reviewed scoped mapping.
- A known thread arrives with a conflicting address: stop and review.
- Two open deals share one contact: use explicit enrollment or review.
- A blocked contact is re-imported: the block remains effective.
- An owner changes while a send is queued: stale routing cannot silently send.
- The same reply event arrives twice: one follow-up activity.
Quick answers
Should the integration create a contact for every unknown reply?+
Only under an explicit policy. Preserve and review unknown or ambiguous replies first; blindly creating contacts can multiply duplicates and route messages incorrectly.
Can we use the incoming from field as the customer phone?+
Miss Blue documents from as an opaque platform identifier. Use sender_handle when present, or a reviewed mapping for the scoped thread.
Does pinning a conversation assign the CRM owner?+
No. A personal pin controls that member’s list order. CRM ownership and an external automation’s pause state must be managed by the integration.