SSiddiq.in
CRM plumbing, done properly

The CRM isn't your problem. The three systems trying to write to it are.

Every mid-sized business has a website form, a WhatsApp bot, an ads platform, and a spreadsheet all trying to feed the same CRM — usually with different field names for the same data. We fix that layer, so your reporting stops lying to your board.

Source-of-truth map · week 1 deliverable

Every field has one owner. That's the whole game.

FieldOwnerReadersConflict rule
emailWebsite formCRM, Mailchimp, WhatsAppWebsite wins — validated at capture
phone (E.164)WhatsApp opt-inCRM, calling appWhatsApp wins — verified via OTP
lifecycle_stageCRM (Sales)Website (personalisation)CRM wins always
last_touch_sourceAds / analyticsCRM reportAnalytics wins — CRM read-only
deal_amountCRM (Sales)FinanceCRM wins — Finance reads
support_tierBilling systemCRM, support deskBilling wins — nightly sync
The pattern that stops silent data loss

Outbox + retry — every event lands, eventually.

STEP 1

Event lands in your DB first

New lead, form submit, WhatsApp opt-in — written to an `outbox` table in Postgres. Never straight to the CRM.

STEP 2

Worker delivers to CRM

Reads outbox, calls CRM API, marks delivered on 2xx. On failure — retry with exponential backoff, up to 24h.

STEP 3

Undeliverables → human queue

After 24h of failure it lands in a Slack channel a human can triage. Zero silent drops.

Deduplication ladder

Five checks before we create a new contact.

RULE 1
Exact email match
Auto-merge, keep older CRM id
RULE 2
E.164 phone match
Auto-merge, keep older CRM id
RULE 3
Same email domain + same first + last name
Flag for human review
RULE 4
Same company + same first + last name
Flag for human review
RULE 5
No match after all checks
Create new contact, mark source

CRM FAQ

Because they're modelled as 'push all leads' instead of 'reconcile two systems'. The moment field mappings drift, duplicates appear, custom fields get renamed, or a webhook silently fails — the sync degrades. We build for reconciliation, not fire-and-forget.

Sync conflict patterns

The six conflicts every two-way CRM sync has to resolve.

Bi-directional sync is easy on the whiteboard, painful in production. These are the six decisions you make before the first record moves.

Winning side per field
Field-level rules, not record-level. 'Marketing owns lifecycle_stage; Sales owns deal_stage.' Written down.
Last-write-wins guardrails
Add a source_updated_at + source_system. LWW is fine when clocks are trustworthy; drop the write when they're not.
Deletes as tombstones
Never hard-delete on sync. Mark deleted_at + reason. Restores are one-click; hard-delete is a Sunday afternoon.
Deduplication key
Email is not unique enough. Use email + phone + normalized name. Confidence-scored merge, human-approved above 0.85.
Enum mapping
'Qualified' in HubSpot ≠ 'MQL' in Salesforce. Store the mapping table in code, not as a spreadsheet in someone's inbox.
Timezone + locale normalization
Store UTC + IANA zone on every timestamp. Display in user local. Never trust the sending system to have done this right.

Get a CRM your board can trust.

Book an audit
Right tech, honest advice

Not sure if this is the right stack for you?

Tell us your goals — we'll recommend the right technology honestly, even if it isn't this one.

  • Personal reply from Siddiq within 24 hours
  • Honest advice — no pushy sales, no fake urgency
  • Clear next steps, timeline, and estimate

We only use your details to reply to this enquiry. Privacy.