Each CRM broken out separately — every standard object as its own table with that tool's real API column names, mapped to a DevRev target. Map to a DevRev standard first. Only tag CUSTOM when no standard exists. Where a clean replacement genuinely requires a DevRev standard object/field that doesn't exist today, it's flagged ⚑ STD-NEEDED — a gap to raise with product, not something to bury in custom.
describeSObject() (SFDC), Fields API (Zoho), Properties API (HubSpot), Get-Metadata (LeadSquared) against an authenticated org — vendor doc portals are JS SPAs and don't expose field tables to fetch.
Mapping the wrong identity object is the #1 migration defect.
external_ref.external_uid (fresh_custom recipe), not external_ref.Field names = SFDC SOAP/REST API names. Id → external_ref/external_uid; SystemModstamp → delta cursor on every object.
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_ref | NATIVE | + paired Rev Org external_ref |
Name | display_name | NATIVE | |
Website | domain / website | NATIVE | drives auto-linking |
Industry | custom.industry | CUSTOM | enum; pre-seed picklist |
AnnualRevenue | custom.annual_revenue | CUSTOM | minor units |
NumberOfEmployees | custom.employee_count | CUSTOM | |
Phone | custom.phone | CUSTOM | |
BillingStreet / BillingCity / BillingState / BillingPostalCode / BillingCountry | custom.billing_street / _city / _state / _zip / _country | CUSTOM | no compound-address type — flatten to 5 fields |
ShippingStreet … ShippingCountry | custom.shipping_street / _city / _state / _zip / _country | CUSTOM | separate prefixed set |
Type | tier | ⚑ STD-NEEDED | Account-tier/segment standard field, if not enabled |
ParentId | custom.parent_account | CUSTOM | self-ref; load parents first |
OwnerId | owned_by | NATIVE | resolve → devu |
SystemModstamp | audit fields | NATIVE | delta cursor |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_ref | NATIVE | |
FirstName/LastName | display_name / full_name | NATIVE | |
Email | NATIVE | dedup/merge key | |
Phone/MobilePhone | phone_numbers[] | NATIVE | |
Title | custom.job_title | CUSTOM | |
AccountId | rev_org / account | NATIVE | must resolve, else orphan revu |
OwnerId | owned_by | NATIVE | → devu |
LeadSource | custom.lead_source | CUSTOM | |
HasOptedOutOfEmail | custom.email_opt_out | CUSTOM | |
MailingStreet / MailingCity / MailingState / MailingPostalCode / MailingCountry | custom.mailing_street / _city / _state / _zip / _country | CUSTOM | flatten to 5 fields |
SystemModstamp | audit fields | NATIVE |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_uid | ⚑ STD-NEEDED | native Lead object gated; else custom object |
Name | display_name | NATIVE | |
Company | custom.company | CUSTOM | text until converted (no Account yet) |
Email/Phone | custom.email / custom.phone | CUSTOM | |
Status | stage | NATIVE | build stage diagram; zero-pad enum prefixes |
LeadSource | custom.lead_source | CUSTOM | |
Rating | custom.score | CUSTOM | |
OwnerId | owned_by | NATIVE | → devu |
IsConverted | custom.is_converted (bool) | CUSTOM | |
ConvertedAccountId / ConvertedContactId / ConvertedOpportunityId | custom.converted_account / _contact / _opportunity | CUSTOM | each stores target external_ref |
SystemModstamp | audit fields | NATIVE |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_uid | ⚑ STD-NEEDED | native Opportunity gated; else custom object |
Name | display_name | NATIVE | |
AccountId | custom.account (ref) | NATIVE | resolve to Account |
Amount | custom.amount (int) | CUSTOM | minor units |
CurrencyIsoCode | custom.currency (enum) | CUSTOM | multi-currency orgs |
StageName | stage | NATIVE | map pipeline 1:1; deprecate dropped stages |
Probability | custom.probability | CUSTOM | |
CloseDate | custom.close_date (ts) | CUSTOM | optionalTs (empty ts breaks loader) |
ForecastCategory | custom.forecast_category | CUSTOM | |
OwnerId | owned_by | NATIVE | → devu |
OpportunityLineItem[] | child custom object | CUSTOM | links Product2/PricebookEntry |
SystemModstamp | audit fields | NATIVE |
CRM Product = sellable catalog item (SKU, price, on quotes/orders). Not a DevRev Part — Part is the internal product/feature tree for issue routing. Model as a product-catalog custom object.
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_uid | CUSTOM | product-catalog custom object |
Name | custom.name | CUSTOM | |
ProductCode | custom.sku | CUSTOM | SKU = field on catalog object |
Description | custom.description | CUSTOM | |
Family | custom.family | CUSTOM | category/grouping |
IsActive | custom.is_active | CUSTOM | |
SystemModstamp | audit fields | CUSTOM | delta cursor |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Pricebook2.Id | external_uid (custom obj) | CUSTOM | reference data; load once |
Pricebook2.Name | custom.name | CUSTOM | |
Pricebook2.IsActive | custom.is_active | CUSTOM | |
PricebookEntry.Product2Id | custom.product (ref → product-catalog obj) | CUSTOM | |
PricebookEntry.UnitPrice | custom.unit_price (int) | CUSTOM | minor units |
PricebookEntry.CurrencyIsoCode | custom.currency | CUSTOM |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_ref | NATIVE | owner-resolution join key |
Name | Dev User display_name | NATIVE | |
Email | Dev User email | NATIVE | |
ProfileId/UserRoleId | custom.role | CUSTOM | |
IsActive | state | NATIVE | keep deactivated (historical refs) |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Quote.Id | external_uid | CUSTOM | header custom object |
Quote.Name | custom.name | CUSTOM | |
Quote.OpportunityId | custom.opportunity (ref) | CUSTOM | |
Quote.Status | stage | CUSTOM | stage diagram |
Quote.TotalPrice / GrandTotal | custom.total (int) | CUSTOM | minor units |
Quote.ExpirationDate | custom.expiration_date | CUSTOM | optionalTs |
QuoteLineItem.* | child custom object | CUSTOM | Product2Id, Quantity, UnitPrice |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Order.Id | external_uid | CUSTOM | header custom object |
Order.OrderNumber | custom.order_number | CUSTOM | |
Order.AccountId | custom.account (ref) | CUSTOM | |
Order.Status | stage | CUSTOM | |
Order.TotalAmount | custom.total (int) | CUSTOM | minor units |
Order.EffectiveDate / EndDate | custom.start_date / _end_date | CUSTOM | optionalTs |
OrderItem.* | child custom object | CUSTOM | Product2Id, Quantity, UnitPrice |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Id | external_uid | CUSTOM | |
ContractNumber | custom.contract_number | CUSTOM | |
AccountId | custom.account (ref) | CUSTOM | |
Status | stage | CUSTOM | |
StartDate / EndDate | custom.start_date / _end_date | CUSTOM | optionalTs |
ContractTerm | custom.term_months (int) | CUSTOM |
| API column | DevRev target | Tag | Notes |
|---|---|---|---|
Campaign.Id | external_uid | CUSTOM | |
Campaign.Name | custom.name | CUSTOM | |
Campaign.Type / Status | custom.type / custom.status | CUSTOM | |
Campaign.StartDate / EndDate | custom.start_date / _end_date | CUSTOM | optionalTs |
CampaignMember.ContactId / LeadId | join custom object → revu | CUSTOM | member = Contact↔Campaign join |
CampaignMember.Status | custom.member_status | CUSTOM |
Field names = Zoho api_name. id → external_ref/external_uid; Modified_Time → delta cursor.
| api_name | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_ref (+Rev Org) | NATIVE | |
Account_Name | display_name | NATIVE | |
Website | domain / website | NATIVE | |
Industry | custom.industry | CUSTOM | |
Annual_Revenue | custom.annual_revenue | CUSTOM | |
Employees | custom.employee_count | CUSTOM | |
Phone | custom.phone | CUSTOM | |
Billing_Street / Billing_City / Billing_State / Billing_Code / Billing_Country | custom.billing_street / _city / _state / _zip / _country | CUSTOM | + Shipping_* → custom.shipping_* |
Account_Type | tier | ⚑ STD-NEEDED | if tier standard not enabled |
Parent_Account | custom.parent_account | CUSTOM | |
Owner | owned_by | NATIVE | → devu |
Modified_Time | audit fields | NATIVE | delta cursor |
| api_name | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_ref | NATIVE | |
First_Name/Last_Name | display_name / full_name | NATIVE | |
Email | NATIVE | merge key | |
Phone/Mobile | phone_numbers[] | NATIVE | |
Title | custom.job_title | CUSTOM | |
Account_Name (lookup) | rev_org / account | NATIVE | resolve |
Lead_Source | custom.lead_source | CUSTOM | |
Email_Opt_Out | custom.email_opt_out | CUSTOM | |
Owner | owned_by | NATIVE | |
Modified_Time | audit fields | NATIVE |
| api_name | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_uid | ⚑ STD-NEEDED | native Lead gated; else custom object |
Full_Name | display_name | NATIVE | |
Company | custom.company | CUSTOM | |
Email/Phone | custom.email / custom.phone | CUSTOM | |
Lead_Status | stage | NATIVE | stage diagram |
Lead_Source | custom.lead_source | CUSTOM | |
Rating | custom.score | CUSTOM | |
Owner | owned_by | NATIVE | |
Modified_Time | audit fields | NATIVE |
| api_name | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_uid | ⚑ STD-NEEDED | native Opportunity gated; else custom object |
Deal_Name | display_name | NATIVE | |
Account_Name | custom.account (ref) | NATIVE | resolve |
Amount | custom.amount | CUSTOM | minor units |
Currency | custom.currency | CUSTOM | |
Stage | stage | NATIVE | 1:1 mapping |
Probability | custom.probability | CUSTOM | |
Closing_Date | custom.close_date | CUSTOM | optionalTs |
Pipeline | custom.pipeline | CUSTOM | |
Owner | owned_by | NATIVE | |
Modified_Time | audit fields | NATIVE |
Sellable catalog item → product-catalog custom object (not DevRev Part).
| api_name | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_uid | CUSTOM | product-catalog custom object |
Product_Name | custom.name | CUSTOM | |
Product_Code | custom.sku | CUSTOM | |
Product_Category | custom.family | CUSTOM | category/grouping |
Product_Active | custom.is_active | CUSTOM | |
Unit_Price | custom.unit_price | CUSTOM | minor units |
| api_name | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_ref | NATIVE | owner join key |
full_name | Dev User display_name | NATIVE | |
email | Dev User email | NATIVE | join key |
role / profile | custom.role | CUSTOM | |
status | state | NATIVE | keep deactivated |
| Module · key api_name fields | DevRev target | Tag | Notes |
|---|---|---|---|
Price_Books: Price_Book_Name, Pricing_Details | custom object | CUSTOM | reference data; load once |
Quotes: Subject, Deal_Name, Grand_Total, Valid_Till, Quote_Stage | custom object (+child Quoted_Items) | CUSTOM | Grand_Total minor units; Valid_Till optionalTs |
Sales_Orders: Subject, Account_Name, Grand_Total, Status | custom object (+child Ordered_Items) | CUSTOM | |
Subscriptions: Name, Status, Start_Date, Billing_Frequency | custom object | CUSTOM | stage diagram for status |
Campaigns: Campaign_Name, Type, Status, Start_Date, End_Date | custom object | CUSTOM | member link = join object |
Field names = HubSpot internal property names. hs_object_id → external_ref/external_uid (string); hs_lastmodifieddate → delta cursor.
| property | DevRev target | Tag | Notes |
|---|---|---|---|
hs_object_id | external_ref (+Rev Org) | NATIVE | |
name | display_name | NATIVE | |
domain | domain | NATIVE | HubSpot unique key |
industry | custom.industry | CUSTOM | |
annualrevenue | custom.annual_revenue | CUSTOM | |
numberofemployees | custom.employee_count | CUSTOM | |
phone | custom.phone | CUSTOM | |
address / city / state / zip / country | custom.address_street / _city / _state / _zip / _country | CUSTOM | flatten to 5 fields |
hubspot_owner_id | owned_by | NATIVE | → devu (via Owners API) |
hs_lastmodifieddate | audit fields | NATIVE | delta cursor |
| property | DevRev target | Tag | Notes |
|---|---|---|---|
hs_object_id | external_ref | NATIVE | |
firstname/lastname | display_name / full_name | NATIVE | |
email | NATIVE | unique key | |
phone/mobilephone | phone_numbers[] | NATIVE | |
jobtitle | custom.job_title | CUSTOM | |
| associated company | rev_org / account | NATIVE | via associations API |
lifecyclestage | custom.lifecycle_stage | CUSTOM | lead/MQL/SQL/customer |
hs_lead_status | stage (if lead) | NATIVE | lifecycle=lead → Lead flow |
hs_analytics_source | custom.lead_source | CUSTOM | |
hubspot_owner_id | owned_by | NATIVE | |
hs_lastmodifieddate | audit fields | NATIVE |
HubSpot has no separate Lead object — a lead is a Contact with lifecyclestage=lead. Route to Lead flow only if the connector splits on lifecycle.
| property | DevRev target | Tag | Notes |
|---|---|---|---|
hs_object_id | external_uid | ⚑ STD-NEEDED | native Opportunity gated; else custom object |
dealname | display_name | NATIVE | |
| associated company | custom.account (ref) | NATIVE | via associations |
amount | custom.amount | CUSTOM | minor units |
deal_currency_code | custom.currency | CUSTOM | |
dealstage | stage | NATIVE | 1:1; internal stage IDs |
pipeline | custom.pipeline | CUSTOM | multi-pipeline |
hs_deal_stage_probability | custom.probability | CUSTOM | |
closedate | custom.close_date | CUSTOM | optionalTs |
hubspot_owner_id | owned_by | NATIVE | |
| line items (0-8) | child custom object | CUSTOM | via associations |
hs_lastmodifieddate | audit fields | NATIVE |
Sellable catalog item → product-catalog custom object (not DevRev Part).
| property | DevRev target | Tag | Notes |
|---|---|---|---|
hs_object_id | external_uid | CUSTOM | product-catalog custom object |
name | custom.name | CUSTOM | |
hs_sku | custom.sku | CUSTOM | |
description | custom.description | CUSTOM | |
price | custom.unit_price | CUSTOM | minor units |
hs_lastmodifieddate | audit fields | CUSTOM | delta cursor |
| field | DevRev target | Tag | Notes |
|---|---|---|---|
id | external_ref | NATIVE | = hubspot_owner_id refs |
firstName / lastName | Dev User display_name | NATIVE | |
email | Dev User email | NATIVE | join key |
archived | state | NATIVE | keep archived (historical refs) |
| property | DevRev target | Tag | Notes |
|---|---|---|---|
line_item.name | custom.name | CUSTOM | child of Deal |
line_item.hs_product_id | custom.product (ref → product-catalog obj) | CUSTOM | |
line_item.quantity / price | custom.quantity / custom.unit_price | CUSTOM | minor units |
quote.hs_title | custom.name | CUSTOM | quote header custom object |
quote.hs_status | stage | CUSTOM | |
quote.hs_expiration_date | custom.expiration_date | CUSTOM | optionalTs |
Schema field names via Get-Metadata. ProspectID / OpportunityId → external ids. Created-date biased — use hybrid delta (short modified window + periodic full sweep).
| schema field | DevRev target | Tag | Notes |
|---|---|---|---|
ProspectID | external_ref (revu) | NATIVE | LS Lead = person → Rev User |
FirstName/LastName | display_name / full_name | NATIVE | |
EmailAddress | NATIVE | merge key | |
Phone/Mobile | phone_numbers[] | NATIVE | |
Company | account / custom.company | ⚑ STD-NEEDED | LS has no first-class Account for B2C; create Account+Rev Org if B2B |
ProspectStage | stage | NATIVE | stage diagram |
Source | custom.lead_source | CUSTOM | |
Score/EngagementScore | custom.score | CUSTOM | |
OwnerId/Owner | owned_by | NATIVE | → devu |
ModifiedOn | audit fields | NATIVE | delta (created-biased → hybrid) |
CreatedOn | audit fields | NATIVE | primary window field |
| schema field | DevRev target | Tag | Notes |
|---|---|---|---|
OpportunityId | external_uid | ⚑ STD-NEEDED | native Opportunity gated; else custom object |
OpportunityName | display_name | NATIVE | |
ProspectID (owner lead) | custom.contact (ref) | NATIVE | link to revu |
Amount/Value | custom.amount | CUSTOM | minor units |
Stage | stage | NATIVE | 1:1 |
Status | custom.status | CUSTOM | |
ExpectedClosingDate | custom.close_date | CUSTOM | optionalTs |
OwnerId | owned_by | NATIVE | |
ModifiedOn | audit fields | NATIVE |
| schema field | DevRev target | Tag | Notes |
|---|---|---|---|
ActivityId / ProspectActivityId | Timeline entry id (external_ref) | NATIVE | |
RelatedProspectId | parent revu (link) | NATIVE | entry attaches to Rev User |
ActivityEvent / ActivityType | custom.activity_type | CUSTOM | enum; pre-seed activity-type picklist |
ActivityNote / Notes | timeline comment body | NATIVE | |
Status | custom.status | CUSTOM | |
Owner / CreatedBy | created_by (devu) | NATIVE | → devu |
CreatedOn | timeline entry timestamp | NATIVE | |
ModifiedOn | audit fields | NATIVE | delta cursor |
| schema field | DevRev target | Tag | Notes |
|---|---|---|---|
TaskId / Id | external_ref | NATIVE | |
Name / Subject | title | NATIVE | |
Description / Notes | body | NATIVE | |
Status | stage | NATIVE | open/in_progress/completed |
Priority | priority | NATIVE | map High/Med/Low → P0–P3 |
DueDate / ReminderTime | custom.due_date (ts) | CUSTOM | optionalTs |
RelatedProspectId / RelatedEntityId | linked object | NATIVE | resolve target (revu/opportunity) |
Category / TaskType | custom.activity_type | CUSTOM | |
UserId / OwnerId | owned_by (devu) | NATIVE | → devu |
ModifiedOn | audit fields | NATIVE | delta cursor |
| schema field | DevRev target | Tag | Notes |
|---|---|---|---|
UserId | external_ref | NATIVE | owner-resolution join key |
FirstName / LastName | Dev User display_name | NATIVE | |
EmailAddress | Dev User email | NATIVE | join key |
Role | custom.role | CUSTOM | |
IsActive / Status | state | NATIVE | keep deactivated (historical refs) |
Every CRM logs interactions as "activities". Coverage differs. This is what feeds the polymorphic Task / Calls / email recommendations below.
| Activity | Salesforce | Zoho CRM | HubSpot | LeadSquared | DevRev target |
|---|---|---|---|---|---|
| Task / To-do | Task (polymorphic WhoId/WhatId) | Tasks | task engagement (0-27) | Task entity (Task/Create) | Task (work) — polymorphic ⚑ |
| Call / Phone | Task w/ TaskSubtype=Call, CallDurationInSeconds, CallDisposition | Calls module (Call_Type, Call_Duration, Call_Result) | call engagement (recording, disposition, duration) | phone-call Activity (ActivityEvent code) | Calls object ⚑ |
| Meeting / Event | Event (calendar, polymorphic) | Meetings/Events (Activities sub-module) | meeting engagement (attendees, outcome) | meeting Activity | Timeline entry / Calls-style object |
EmailMessage + EmailMessageRelation (to/cc/bcc); Task subtype=Email | Emails (Sent/Received, per record) | email engagement (open/click events) | email Activity + Email campaigns | Timeline entry (email) + Email composer ⚑ | |
| Note | ContentNote / Note | Notes module | note engagement | Note | Timeline comment |
| Custom activity | Task/Event subtypes + custom fields | custom Activities | custom engagement props | custom Activity types (numeric ActivityEvent; heavy usage — enumerate first) | custom.activity_type enum / Timeline |
| System / auto activity | field-history, FeedItem | Blueprint transitions | lifecycle-stage change, page-visit, form-submit | status-change, page-visit, email-opened (system ActivityEvents) | Timeline entries (system) |
Task subtypes (+ Event for meetings). Zoho splits into distinct modules (Calls / Meetings / Emails / Tasks / Notes). HubSpot = 5 unified engagement objects on one timeline. LeadSquared models nearly everything as an Activity keyed by a numeric ActivityEvent — Task is the one distinct entity; custom activity types are pervasive and must be enumerated via metadata before extraction or history is lost. ⚑ STD-NEEDED = drives the Calls-object & polymorphic-Task recommendations.Salesforce, Zoho, HubSpot, LeadSquared all let a rep compose & send email from the record (Lead/Contact/Deal) with templates, merge fields, and attachments. DevRev has a rich email/compose editor on the support/conversation side — the CRM/sales surface has no equivalent. A clean replacement needs a sales-record email composer. Flag as product gap; interim = reuse the support conversation editor scoped to the record, or a snap-in.
| Capability | Source CRM equivalent | DevRev today | Tag |
|---|---|---|---|
| Compose from Lead/Contact/Deal record | SFDC Activity email · HubSpot "Email" tab · Zoho Send Mail · LS Send Email | only support conversations / PLuG | ⚑ STD-NEEDED |
| Rich HTML body + plain-text fallback | all four (WYSIWYG + source) | rich text in timeline/conversation only | ⚑ STD-NEEDED |
| Templates (save / pick / share) | SFDC Email Templates · HubSpot Templates · Zoho Email Templates · LS Templates | no sales-email template store | ⚑ STD-NEEDED |
| Merge / personalization tokens | {{Contact.FirstName}}, deal fields, owner sig | none on sales records | ⚑ STD-NEEDED |
| Attachments | files + content-library docs | Artifacts exist (attach + vectorize) | NATIVE* |
| Logged/threaded on the record timeline | email logged to Activity timeline | Timeline entries native | NATIVE |
| Send + delivery/open tracking | all four track sends/opens | no sales-side send infra | ⚑ STD-NEEDED |
* Attachments: the Artifact primitive exists — wire the composer to attach Artifacts. Body storage can reuse rich_text (HTML) + a derived plain-text field; EDM/IDM must agree on the type (rich_text split-brain rejects on recipe discovery).
| Source object | DevRev target | Tag | Notes |
|---|---|---|---|
Email template (EmailTemplate / HubSpot template / Zoho / LS) | native Template object (subtype=email) | ⚑ STD-NEEDED | one store for all channels — see Template object |
Sent email (EmailMessage / engagement / activity) | Timeline entry (email type) | NATIVE | from/to/subject/body/sent_at; attachments → Artifacts |
| Email attachments | Artifacts | NATIVE | link to the timeline entry |
CRMs drive outreach over voice, SMS, and WhatsApp — not just email. To replace one, DevRev needs generic, provider-agnostic connectors for each channel that support multiple profiles (many numbers/senders/accounts per org) and template sync (pull approved templates from the provider, incl. India DLT & WhatsApp HSM). All channel events land on the record timeline; recordings/media → Artifacts.
| Channel | What it must do | Providers to abstract | Tag |
|---|---|---|---|
| Telephony / CTI (voice) | click-to-call, inbound routing, call recording→Artifact, transcript, disposition, duration, direction; screen-pop the matched record | Twilio · Exotel · Ozonetel · Knowlarity (Gupshup) · Servetel/Tata Tele · Airtel IQ · MyOperator · Plivo · Amazon Connect · Genesys · generic SIP | ⚑ STD-NEEDED |
| SMS | send/receive, DLR (delivery receipts), sender-ID profiles, template + DLT sync (India), opt-out/STOP handling | Twilio · MSG91 · Gupshup · Netcore · Kaleyra (Tata) · Sinch · Karix · Infobip · Plivo · Vonage · Airtel IQ | ⚑ STD-NEEDED |
| WhatsApp (Business) | WABA profiles, HSM/approved-template sync, session vs template messages, media, 24h window rules, opt-in | Meta Cloud API (direct) · Twilio · Gupshup · Netcore · WATI · Interakt · AiSensy · Gallabox · Zoko · Haptik · Yellow.ai · DoubleTick · 360dialog · Infobip · Kaleyra · MSG91 · Karix · Wappnet · Charles · Verloop | ⚑ STD-NEEDED |
| RCS / other IM (future) | same shape as SMS/WhatsApp | provider-dependent | ⚑ STD-NEEDED |
| Requirement | Detail |
|---|---|
| Provider-agnostic interface | one channel abstraction, provider = a pluggable adapter (like AirSync's connector shape). Swap Twilio↔MSG91 without changing the record surface. |
| Multiple profiles | an org runs many senders — several phone numbers / short codes / sender IDs / WABA numbers, each with its own keyring. Route by brand/team/campaign. Profile = credential set + sender identity. |
| Template sync | pull provider-side approved templates on a schedule: WhatsApp HSM (name, language, category, variables, approval status), SMS DLT (India — template ID, entity ID, header/sender), email templates. Read-only mirror + local overrides. |
| Merge / variables | template placeholders bound to record fields ({{1}},{{2}} for WhatsApp; named tokens for SMS/email). |
| Inbound + outbound on timeline | every message/call = a typed Activity/Timeline entry on the matched Contact/Lead/Opp; media & recordings → Artifacts. |
| Delivery/read receipts & events | sent/delivered/read/failed, call disposition, DLR — captured as status on the entry. |
| Consent & compliance | opt-in/opt-out per channel, STOP keyword, WhatsApp 24h session window, DLT registration, TCPA/DND — enforce before send. |
| Keyring / secrets | provider creds in developer_keyrings per profile — never in manifest; OAuth or API-key per provider. |
Popular CPaaS / telephony / WhatsApp-BSP vendors. ✓ = strong offering. Prioritize by customer footprint — India POCs skew MSG91 / Gupshup / Exotel / Netcore; global skews Twilio / Infobip / Sinch.
| Vendor | Voice/CTI | SMS | Region strength | Notes | |
|---|---|---|---|---|---|
| Twilio | ✓ | ✓ | ✓ | Global | broadest API; Programmable Voice/Messaging; WhatsApp via Twilio |
| MSG91 | ✓ | ✓ | ✓ | India | DLT-native SMS; OTP; WhatsApp; Hello voice |
| Gupshup | ✓ (Knowlarity) | ✓ | ✓ | India / EM | major WhatsApp BSP; acquired Knowlarity (voice) |
| Netcore | — | ✓ | ✓ | India / SEA | CPaaS + CE; email (Pepipost); WhatsApp + RCS |
| Exotel | ✓ | ✓ | ✓ | India / SEA | cloud telephony leader; IVR, call center, number masking |
| Ozonetel | ✓ | ✓ | ✓ | India / US | CloudAgent contact center; CTI connectors |
| Kaleyra (Tata Comms) | ✓ | ✓ | ✓ | India / Global | enterprise CPaaS; now part of Tata Communications |
| Infobip | ✓ | ✓ | ✓ | Global / EMEA | omnichannel CPaaS; strong WhatsApp/Viber/RCS |
| Sinch | ✓ | ✓ | ✓ | Global | CPaaS; SMS/voice/WhatsApp/RCS |
| Plivo | ✓ | ✓ | ✓ | Global / India | voice + messaging APIs; competitive pricing |
| Vonage (Ericsson) | ✓ | ✓ | ✓ | Global | Nexmo APIs; voice/SMS/WhatsApp |
| Airtel IQ | ✓ | ✓ | ✓ | India | telco-native CPaaS; carrier-grade delivery |
| Tata Tele / Servetel | ✓ | ✓ | — | India | cloud telephony, toll-free, IVR |
| Knowlarity | ✓ | ✓ | ✓ | India / SEA | SuperReceptionist; now under Gupshup |
| MyOperator | ✓ | ✓ | ✓ | India | SMB cloud call management |
| WATI · Interakt · AiSensy · Gallabox · Zoko · DoubleTick · Wappnet | — | — | ✓ | India / SMB | WhatsApp-first BSPs; shared-inbox, template + broadcast/campaign tooling, chatbot builders |
| 360dialog · Charles · Zoko · Rasayel | — | — | ✓ | Global / EU | WhatsApp BSPs; 360dialog = direct Cloud-API reseller (no markup on conversations) |
| Haptik · Yellow.ai · Verloop · Gupshup Bot | — | — | ✓ | India / Global | conversational-AI BSPs — WhatsApp + bot/agent-assist layer on top |
| Amazon Connect · Genesys · Five9 · NICE | ✓ | ✓ | ✓ | Global (enterprise CC) | full contact-center platforms; heavier CTI integration |
| Bird (MessageBird) | ✓ | ✓ | ✓ | Global / EMEA | omnichannel CPaaS; WhatsApp BSP |
| Telnyx | ✓ | ✓ | ✓ | Global / US-EU | carrier-grade voice/SMS; private network |
| Bandwidth | ✓ | ✓ | — | US / Global | owns network; enterprise voice/messaging + 911 |
| RingCentral · 8x8 · Dialpad | ✓ | ✓ | ✓ | US / Global | UCaaS/CCaaS; embedded CTI, native CRM dialers |
| Telesign · Bandyer/Kaleyra Video | — | ✓ | — | Global | verification/OTP-heavy SMS |
| Zenvia · Take Blip | — | ✓ | ✓ | LATAM (Brazil) | regional WhatsApp/SMS leaders |
| Africa's Talking · Clickatell | ✓ | ✓ | ✓ | Africa | Clickatell = early WhatsApp BSP; strong Africa/EM |
| Unifonic · Cequens | ✓ | ✓ | ✓ | MENA | Middle East regional CPaaS |
| LINE · KakaoTalk · Viber · Zalo · WeChat | — | — | ✓* | APAC / regional IM | *channel-equivalents to WhatsApp — JP/KR/VN/CN/SEA; abstract as messaging channels |
Don't scatter templates across per-connector customs. One standard Template object with a common core + a channel-typed field set (differentiated by channel/provider). subtype = channel (email / sms / whatsapp / voice-script) selects which vendor-specific fields apply. Filter/query by channel · provider · profile · approval_status.
| Field | Scope | Type | Note |
|---|---|---|---|
subtype (channel) | core | enum | email · sms · whatsapp · voice — selects vendor field set |
name | core | text | |
provider | core | enum | Twilio · MSG91 · Gupshup · Meta … — which BSP/CPaaS owns it |
profile | core | ref | which sender/WABA/number profile |
body | core | rich_text | text/HTML per channel |
variables[] | core | list | merge tokens ({{1}}, named) |
language | core | enum | |
approval_status | core | enum | draft · submitted · approved · rejected · paused — synced from provider |
external_template_id | core | id | provider's template id (sync key) |
subject | text | email only | |
from / reply_to | text | email only | |
hsm_category | enum | MARKETING / UTILITY / AUTHENTICATION | |
header / footer / buttons | struct | HSM components; media header | |
quality_rating | enum | green/yellow/red — from Meta | |
dlt_template_id / entity_id / sender_id | sms | id | India DLT registration |
us_campaign_id (10DLC) | sms | id | US brand/campaign reg |
script_steps / ivr_flow | voice | struct | call script / IVR |
approval_status/quality_rating (pull on schedule); DevRev owns local drafts + which profile/brand uses which template. Block send on non-approved/paused. Same shape covers email templates too — unifies the email composer template need.| Source | DevRev target | Tag |
|---|---|---|
| SMS/WhatsApp templates (DLT/HSM), call scripts | native Template object (subtype=channel) — see above | ⚑ STD-NEEDED |
| Call logs (SFDC Task/Call, HS call, Zoho Calls, LS activity) | Calls object / Activity (see recos) | ⚑ |
| SMS / WhatsApp message history | Timeline entries (typed) | NATIVE |
| Recordings / media | Artifacts | NATIVE |
To be a true CRM replacement — not a data mirror — DevRev needs these as standard objects/behaviours. Prioritized.
| Requirement | Detail |
|---|---|
| Separate object (not a Rev User flag) | Unqualified prospect w/ its own lifecycle, before Account/Contact exist. All 4 CRMs have it. |
| Stage lifecycle | New → Working → Qualified → Converted/Disqualified. Stage diagram, 1:1 with source Lead_Status. |
| Convert action | Lead → Account + Contact(revu) + Opportunity in one step; preserve source external_ref links. |
| Fields | name, company (text pre-convert), email, phone, source, score/rating, owner (devu), stage. |
| Why standard | Custom object loses convert flow, dedup-on-convert, and pipeline UI. Core CRM semantics. |
| Requirement | Detail |
|---|---|
| One Task object, many parents | A Task must attach to Contact, Lead, Account, Opportunity, Ticket, Issue — and ideally Conversation/Part. |
Polymorphic related_to | Mirrors SFDC WhatId/WhoId. DevRev links aren't polymorphic today → this is the gap. Resolve target type at transform until native. |
| Multi-link | e.g. Task linked to both a Contact (who) and an Opportunity (what) simultaneously. |
| Fields | title, body, stage (open/in_progress/completed), priority, due_date, owner (devu), related_to[]. |
| Why standard | Sales activity threads across the whole graph; a per-object custom task fragments reporting & "my open tasks" views. |
| Requirement | Detail |
|---|---|
| Distinct from Task & timeline note | SFDC Task(TaskSubtype=Call) · Zoho Calls · HubSpot Call engagement · LS phone Activity. Own object. |
| Call-specific fields | direction (in/out), duration, call_result/disposition, recording (Artifact), phone_number, call_time. |
| Polymorphic parent | Same as Task — call logs against Contact / Lead / Opportunity / Account. |
| Transcript / recording | recording → Artifact; transcript → timeline entry or body (rich_text, vectorizable). |
| Why standard | Call analytics (volume, disposition, connect rate) need a typed object, not free-text notes. |
| Object / field | Recommendation |
|---|---|
| Opportunity / Deal | First-class standard w/ amount, pipeline, stage, forecast, close_date — confirm plan entitlement or enable. |
| Money + currency type | Native money field (amount + ISO currency in one), not minor-units-in-int. |
| Account tier/segment | Standard field on Account for segmentation/routing. |
| Product-catalog object | Sellable catalog (SKU, price) as first-class sales entity — distinct from Part. |
| Sales email composer | Record-level HTML+text email w/ templates, merge tokens, attachments, send/tracking. |
Synthesis of Salesforce + Zoho + HubSpot + LeadSquared. Inclusion rule: a field/object becomes a Grow standard only if it's a stock (out-of-box) field in ≥3 of 4 CRMs (>75%). This is the minimum standard surface that lets Grow replace a CRM without every customer dropping to custom fields on day one. Coverage column = how many of the 4 tools ship it stock.
subtype (DevRev custom leaf-type / subtype) — the equivalent of SFDC Record Type, HubSpot record type, Zoho Layout. One object, many business variants (Account: Customer/Partner/Prospect · Lead: Inbound/Outbound · Opp: New-Business/Renewal/Upsell). Subtype drives which custom fields, stages, and layouts apply. Already supported in DevRev — standard, not a gap. Preserve source RecordTypeId/Layout → subtype or picklist validation breaks on load.| Grow standard field | Type | cvg | Source stock fields |
|---|---|---|---|
subtype | subtype | 4/4 | record-type: Customer/Partner/Prospect/Reseller — SFDC RecordType · Zoho Layout · HS record type · LS type |
name | text | 4/4 | Name · Account_Name · name · Account |
domain / website | url | 4/4 | Website · Website · domain · Website |
phone | phone | 4/4 | Phone (all) |
industry | enum | 4/4 | Industry (all) |
annual_revenue | money | 3/4 | AnnualRevenue · Annual_Revenue · annualrevenue |
employee_count | int | 3/4 | NumberOfEmployees · Employees · numberofemployees |
billing_address (5 parts) | address | 4/4 | Billing* · Billing_* · address/city… · Address |
account_type / tier | enum | 3/4 | Type · Account_Type · (HS custom) · — |
owner | ref(user) | 4/4 | OwnerId · Owner · hubspot_owner_id · OwnerId |
parent_account | ref(self) | 3/4 | ParentId · Parent_Account · (HS parent) |
created_at / updated_at | ts | 4/4 | audit fields (all) |
| Grow standard field | Type | cvg | Source stock fields |
|---|---|---|---|
subtype | subtype | 4/4 | record-type: Buyer/Influencer/Champion/Technical — SFDC RecordType · Zoho Layout · HS record type |
first_name / last_name | text | 4/4 | all |
email | 4/4 | Email · Email · email · EmailAddress | |
phone / mobile | phone[] | 4/4 | all |
job_title | text | 4/4 | Title · Title · jobtitle · JobTitle |
account | ref(account) | 4/4 | AccountId · Account_Name · assoc company · Company |
lead_source | enum | 4/4 | LeadSource · Lead_Source · hs_analytics_source · Source |
lifecycle_stage | enum | 3/4 | (SFDC status) · Lead_Status · lifecyclestage · ProspectStage |
owner | ref(user) | 4/4 | all |
email_opt_out | bool | 4/4 | HasOptedOutOfEmail · Email_Opt_Out · hs_email_optout · DoNotEmail |
mailing_address | address | 3/4 | Mailing* · Mailing_* · address* |
created_at / updated_at | ts | 4/4 | all |
| Grow standard field | Type | cvg | Source stock fields |
|---|---|---|---|
subtype | subtype | 3/4 | record-type: Inbound/Outbound/Partner-referral — SFDC RecordType · Zoho Layout · LS type |
name | text | 4/4 | all |
company | text (not ref) | 3/4 | free-text — no Account exists pre-conversion; becomes ref via converted_account on convert |
email / phone | email/phone | 4/4 | all |
status / stage | enum(stage) | 4/4 | Status · Lead_Status · hs_lead_status · ProspectStage |
source | enum | 4/4 | LeadSource · Lead_Source · source · Source |
rating / score | int | 4/4 | Rating · Rating · hubspotscore · Score |
owner | ref(user) | 4/4 | all |
converted (flag + refs) | bool + refs | 3/4 | IsConverted+Converted* · Converted · (HS stage change) · Conversion |
| Grow standard field | Type | cvg | Source stock fields |
|---|---|---|---|
subtype | subtype | 4/4 | record-type: New-Business/Renewal/Upsell/Cross-sell — SFDC RecordType · Zoho Layout · HS record type · LS type |
name | text | 4/4 | Name · Deal_Name · dealname · OpportunityName |
account | ref(account) | 4/4 | AccountId · Account_Name · assoc · (via lead) |
amount | money | 4/4 | Amount · Amount · amount · Amount |
currency | enum | 3/4 | CurrencyIsoCode · Currency · deal_currency_code · — |
stage | enum(stage) | 4/4 | StageName · Stage · dealstage · Stage |
pipeline | enum | 3/4 | (SFDC RecordType) · Pipeline · pipeline · — |
probability | percent | 4/4 | Probability · Probability · hs_deal_stage_probability · Probability |
close_date | date | 4/4 | CloseDate · Closing_Date · closedate · ExpectedClosingDate |
owner | ref(user) | 4/4 | all |
forecast_category | enum | 3/4 | ForecastCategory · (Zoho) · hs_forecast_category · — |
line_items | child obj | 4/4 | OpportunityLineItem · Product_Details · line items · — |
| Grow standard field | Type | cvg | Note |
|---|---|---|---|
type | enum(task/call/meeting/email/note) | 4/4 | all log these 5 stock activity types |
subtype | subtype | 3/4 | per-type variant: demo/discovery/follow-up call; SFDC Task/Event RecordType · Zoho · LS custom ActivityEvent |
subject / title | text | 4/4 | all |
body / description | rich_text | 4/4 | all |
related_to[] | polymorphic ref | 4/4 | Account/Contact/Lead/Opp/Ticket/Issue — WhoId/WhatId analog |
owner / assignee | ref(user) | 4/4 | all |
status | enum | 4/4 | open/in_progress/completed |
due_date | ts | 4/4 | task due / activity date |
direction, duration, disposition | enum/int | 3/4 | call-specific (SFDC/Zoho/HS); LS via activity fields |
attachments | Artifact[] | 4/4 | recordings, files |
| Grow standard field | Type | cvg | Source |
|---|---|---|---|
subtype | subtype | 3/4 | record-type: Good/Service/Subscription/Bundle |
name | text | 3/4 | Name · Product_Name · name |
sku / code | text | 3/4 | ProductCode · Product_Code · hs_sku |
unit_price | money | 3/4 | (Pricebook) · Unit_Price · price |
family / category | enum | 3/4 | Family · Product_Category · (HS) |
is_active | bool | 3/4 | IsActive · Product_Active · (HS) |
A sales migration that only maps Account/Contact/Lead/Opportunity silently drops these. Decide migrate vs archive vs drop for each.
| Object | What it holds | Migration note |
|---|---|---|
ContentNote · ContentDocument / ContentVersion / ContentDocumentLink | modern Notes & Files | binary via VersionData; re-link through ContentDocumentLink junction → Artifacts |
Attachment (classic) | legacy attachments | → Artifacts |
OpportunityContactRole / ContactRole | M2M contact↔opp + role (Decision Maker…) | first-class junction; role often dropped |
AccountTeamMember · OpportunityTeamMember · OpportunitySplit | shared ownership / credit split | lost if only Account/Opp migrated |
EmailMessage + EmailMessageRelation | logged emails, to/cc/bcc | → Timeline entry (email); relations = recipients |
Event | calendar entries (polymorphic) | → Calls/meetings object or timeline |
Territory2 · UserTerritory2Association · ObjectTerritory2Association | territory assignment | drives account routing; usually re-modeled |
ForecastingQuota · ForecastingItem | quota/forecast | may need special API enablement — verify accessibility |
UserRole · *Share (AccountShare…) | role hierarchy, sharing | re-model access, don't 1:1 migrate; document implications |
| Module | Note |
|---|---|
Notes | attached to any record via parent id |
Attachments | separate binary download endpoint per record → Artifacts |
Calls · Meetings/Events (Activities sub-modules) | → Calls object / timeline |
Quotes / Sales_Orders / Invoices line items (subforms) | nested line-item arrays inside parent JSON — extract nested, don't flatten away |
Vendors · Price_Books · Purchase_Orders | overlooked in sales migrations |
| Multi-select lookup | M2M via auto-created linking module — extract link records (junction) |
| Custom modules | discover via settings/modules metadata |
| Object / concept | Note |
|---|---|
| Engagements: notes, emails, calls, meetings, tasks | first-class objects; the activity timeline is reconstructed from these + associations — migrating deals w/o engagements loses interaction history |
| Call/email metadata | recordings, disposition, duration, open/click events, meeting attendees |
| Association labels + primary flag | semantic role (Decision maker, Billing contact) lives on the link; primary company keys reporting — lost if only raw links migrated |
| Deal & Ticket pipelines | ordered stages + stage IDs differ per portal; migrate pipeline defs before records |
| Line items / products (0-8 / 0-7) | separate objects via associations |
| Marketing (forms, lists, workflows, campaigns) | Marketing API, outside CRM v3 core — often not in a "CRM" export |
| Custom objects | migrate schema (schemas API) before records |
| Entity | Note |
|---|---|
| Activities incl. custom Activity types | most interaction history = activities keyed by numeric ActivityEvent; enumerate custom activity schemas first or lose history |
| Email campaigns / email history | separate |
| Lists & Segments | static lists vs dynamic segments |
| Sales groups / user hierarchy | manager→rep reporting & ownership |
| Notes · Tasks/Reminders | Task is a distinct entity from Activity |
| Opportunity + custom fields | opportunity mgmt is an add-on — may not be enabled |
| Trap | Where | Handling |
|---|---|---|
| Compound fields (Name, Address, Geolocation) | SFDC | not queryable as one column in Bulk API — extract component subfields (BillingStreet…, __latitude__s); flatten on load |
| Formula / roll-up summary | SFDC, Zoho, HubSpot calc props | computed & read-only — cannot write on load; snapshot resolved value or recompute in DevRev |
| Multi-select picklist | SFDC (A;B;C semicolon), Zoho | split delimiter; target must accept multiple enum/tag values |
| Enum internal value ≠ label | HubSpot, all | map on option internal value, not display label; pre-seed enum (schemas.custom.set strips unknown values) |
| Multi-currency + dated FX | SFDC DatedConversionRate | converted amounts are runtime-only; extract raw amount + CurrencyIsoCode + close date, never converted value |
| Master-detail vs lookup | SFDC | master-detail = cascade delete + roll-ups + required; affects load order & orphan behavior |
| Junction / M2M objects | SFDC junctions, Zoho multi-select lookup | migrate as first-class link records, not flattened |
| Subforms (nested line items) | Zoho quotes/orders/invoices | line items embedded in parent JSON — extract nested |
| Record Types | SFDC | control picklist subsets + layouts; preserve RecordTypeId or picklist validation fails |
| Datetime = UTC / date-only midnight-UTC | HubSpot | normalize TZ; empty ts → optionalTs (loader rejects parsing time "") |
| Owner IDs are internal | HubSpot owners, all | remap to DevRev devu via email, not by raw ID |
/services/data/vXX.0/limits. Zoho → org API-usage dashboard. HubSpot → X-HubSpot-RateLimit-* headers. Numbers below marked verify are best-known, not contractual.| CRM | Rate / daily | Bulk | Notes |
|---|---|---|---|
| Salesforce | daily API = 15,000 base + per-license (Ent +1k/lic, Unl +5k/lic); ~1M/day ceiling (verify); 25 concurrent long-running (Bulk exempt) | Bulk 2.0: server auto-chunks, 150M records/day ingest; ~100M query/day (verify); PK-chunking 100k default/250k max | bulk doesn't burn REST calls; poll status w/ backoff |
| Zoho CRM | credit model per edition/licenses; ~250–2,000 credits/user/day, ~1M/day cap (verify); concurrency ~10–25 | Bulk Read ≤ 200k rec/job; Bulk Write ≤ 25k rec/job (zipped CSV), ~5 concurrent jobs | standard pages max 200/page, ≤2,000 total → use Bulk beyond |
| HubSpot | burst 100–150/10s; daily 250k (Free/Starter) / 500k–650k (Pro/Ent) (verify); API add-on → ~1M/day | batch read/create ≤ 100/req; assoc batch read 1,000 / create 2,000 (verified live) | Search API tighter: 4 req/s, max 10,000 total results, index lag on new records |
| LeadSquared | ~300–500 req/min per key (verify); 429 ExceededApiLimit | Bulk Lead API async, job-based | region-specific host (api-in21 / api-us11…) — wrong host = failure; accessKey+secretKey query auth |
Object-field mapping is necessary, not sufficient. A CRM replacement also needs cutover, dedup, delete/merge detection, automation & reporting rebuild, reconciliation.
queryAll returns deleted+merged) to reconcile.MasterRecordId (survivor←loser), HubSpot merge events. Without merge detection, target keeps both. Reflect as tombstone / redirect, not physical delete.DevRev's multi-brand + membership model. This is the target for CRM org-structure/sharing concepts earlier marked "re-model, don't 1:1 migrate" — divisions, business units, sales teams, territories, public groups.
| Endpoint | Purpose |
|---|---|
/brands.create | create a new brand |
/brands.get (GET & POST) | retrieve a brand by ID |
/brands.list (GET & POST) | list brands; cursor-based pagination + filters |
/brands.update | update an existing brand |
/brands.delete | delete a brand |
Membership flow: create Group → assign devusers to Group → assign Group to Brand and/or Team. Don't attach users to the brand directly.
| Source concept | Tools | DevRev target | Note |
|---|---|---|---|
| Multi-brand / division / business unit | SFDC Division · HubSpot Business Unit · Zoho (portals) · LS: none | Brand | via brands.create; one Brand per source BU/division. LeadSquared has no multi-brand construct — separate LS accounts per brand |
| Sales team / support team | SFDC Public Group / Team · HubSpot Team · Zoho Groups · LS Teams (user-grouping object, not multi-brand) | Team + Group | Team handles brand; Group holds members. LS Teams = distribution/ownership grouping only |
| Sales group / user hierarchy | LeadSquared Sales Groups & Teams · SFDC Role Hierarchy | Group (membership) | manager→rep structure → group membership; hierarchy re-modeled, not 1:1 |
| Territory | SFDC Territory2 | Group / Team assignment | account-routing intent → group→brand/team assignment |
| Sharing rules / *Share | SFDC AccountShare, OpportunityShare | Group-based visibility | access re-modeled via groups; document, don't 1:1 migrate |
| Record owner | all | Dev User (via Group) | owner resolution unchanged; group drives visibility |
external_ref (natives) / external_uid (custom objects). Never derive from name.SystemModstamp · Zoho Modified_Time · HubSpot hs_lastmodifieddate · LeadSquared ModifiedOn (created-biased → hybrid: short modified window + periodic full sweep + content-hash delta).parsing time ""). optionalTs on every nullable date.schemas.custom.set silently strips unknown enum values; pre-seed the full source picklist before first load.Where a clean CRM replacement needs a DevRev standard that isn't available today. Don't bury these in custom — flag them.
| Gap | Affected tools | Why standard, not custom |
|---|---|---|
| Standard Lead object (leaf type + stage lifecycle + convert action) | SFDC, Zoho, HubSpot(lifecycle), LeadSquared | Lead→Account/Contact/Opp conversion is core CRM semantics; custom loses convert flow & pipeline UI |
| Standard Opportunity/Deal object (amount, pipeline, forecast, close date) | All four | Pipeline is the CRM's spine; org/plan-gated today — confirm entitlement or file for enablement |
| Account tier/segment standard field | SFDC Type, Zoho Account_Type | Segmentation drives routing/reporting; belongs on Account standard, not per-connector custom |
| First-class Account for B2C-oriented CRMs | LeadSquared | LS keys on person; B2B rollups need a real Account, not a text field |
| Standard money + currency field type (amount+ISO in one) | All four | Every deal has money; minor-units-in-custom-int is fragile & loses currency semantics |
| Sales-record email composer (HTML+text, templates, merge tokens, attachments, send/tracking) | All four | Reps send email from Lead/Contact/Deal; DevRev email editor is support-side only — see Email-editor section |
| Product-catalog object (SKU, price, catalog) as a first-class sales entity | All four | CRM Product ≠ DevRev Part (Part = internal feature tree); modeled as custom object today, no sales-catalog standard |
| Polymorphic Task linkable to Contact/Lead/Account/Opp/Ticket/Issue | All four | DevRev links aren't polymorphic; six per-object task customs fragment reporting — see Recommendations P0 |
| Calls as a separate object (direction, duration, disposition, recording) | SFDC, Zoho, HubSpot, LS | Call analytics need a typed object, not free-text timeline notes — see Recommendations P1 |
| Generic telephony / SMS / WhatsApp connectors (multi-profile, template + DLT/HSM sync) | All four (outreach channels) | CRMs drive voice/SMS/WhatsApp outreach; DevRev needs provider-agnostic channel connectors — see Telephony & messaging |
| Native Template object (one store, channel/vendor-differentiated fields, provider approval sync) | All four (email/SMS/WhatsApp/voice) | templates shouldn't scatter across per-connector customs; one standard object w/ subtype=channel + HSM/DLT/10DLC fields — see Template object |
Each object below is a DevRev custom leaf type defined via schemas.custom.set (id_prefix + leaf_type_display_name + is_custom_leaf_type) and populated over AirSync (EDM → IDM). The relational DDL is a readable sketch — on-platform each column becomes a custom_schema_fragment field of a DevRev-supported type, and each row is a leaf-type record keyed by external_ref (native mapping) or external_uid (custom leaf).
rich_text split-brain rejects at recipe discovery; fix both halves. (3) Enums pre-seeded before first load (schemas.custom.set silently strips unknown values). (4) Nullable timestamps use optionalTs (empty string → loader parsing time ""). (5) Stages via stage-diagram, deprecate to drop (reachability); enums force-alphabetized → zero-pad prefixes. (6) Secrets in developer_keyrings, referenced by id.| Sketch type | DevRev field type | AirSync note |
|---|---|---|
| text / citext | text | email as text (indexed via is_indexed) |
| text[] (phone_numbers) | tokens (array) | multi-value |
| int / numeric | int / double | |
| bool | bool | |
| timestamptz / date | timestamp | optionalTs on nullable |
| enum / status / stage | enum (+ stage diagram for stage) | pre-seed values |
| rich body | rich_text | EDM/IDM must match |
| FK → object | id reference (typed to leaf) | resolve at transform; load parents first |
| jsonb (address, components) | flatten to fields or struct | prefer flat fields for filtering |
| money (amount+currency) | no native type → int minor-units + enum currency | ⚑ STD-NEEDED native money |
| polymorphic_ref | not supported → join leaf type (activity_link) | ⚑ STD-NEEDED polymorphic reference |
activity_link). Both are in STD-NEEDED — the schema is AirSync-loadable as-is, but these primitives would make it first-class.-- reusable composite + enums
CREATE TYPE money AS (amount_minor bigint, currency char(3));
CREATE TYPE object_kind AS ENUM (
'account','contact','lead','opportunity','product','activity',
'ticket','issue','conversation','part'); -- polymorphic targets
CREATE TYPE channel AS ENUM ('email','sms','whatsapp','voice');
CREATE TYPE approval_status AS ENUM ('draft','submitted','approved','rejected','paused');
CREATE TABLE dev_user (
id text PRIMARY KEY, -- don:identity:devu/...
display_name text NOT NULL,
email citext UNIQUE NOT NULL, -- owner-resolution join key
role text,
state text DEFAULT 'active', -- keep deactivated for historical refs
external_ref text);
CREATE TABLE "group" (
id text PRIMARY KEY,
name text NOT NULL); -- membership primitive
CREATE TABLE group_member ( -- devuser ↔ group (M2M)
group_id text REFERENCES "group"(id),
dev_user_id text REFERENCES dev_user(id),
PRIMARY KEY(group_id, dev_user_id));
CREATE TABLE brand (
id text PRIMARY KEY,
name text NOT NULL,
external_ref text); -- SFDC Division / HS Business Unit
CREATE TABLE team (
id text PRIMARY KEY,
name text NOT NULL);
CREATE TABLE group_assignment ( -- group → brand and/or team
group_id text REFERENCES "group"(id),
brand_id text REFERENCES brand(id),
team_id text REFERENCES team(id));
CREATE TABLE account (
id text PRIMARY KEY,
subtype text, -- Customer/Partner/Prospect (record-type)
name text NOT NULL,
domain text,
phone text,
industry text,
annual_revenue money,
employee_count int,
billing_addr jsonb, -- {street,city,state,zip,country}
tier text,
parent_id text REFERENCES account(id), -- self-ref; load parents first
owner_id text REFERENCES dev_user(id),
rev_org_id text, -- paired Rev Org
external_ref text UNIQUE,
created_at timestamptz, updated_at timestamptz);
CREATE TABLE contact (
id text PRIMARY KEY,
subtype text,
first_name text, last_name text,
email citext, -- dedup/merge key
phone_numbers text[],
job_title text,
account_id text REFERENCES account(id), -- must resolve or orphan
lead_source text,
lifecycle_stage text,
email_opt_out bool DEFAULT false, -- consent — must migrate
mailing_addr jsonb,
owner_id text REFERENCES dev_user(id),
external_ref text UNIQUE,
created_at timestamptz, updated_at timestamptz);
CREATE TABLE lead (
id text PRIMARY KEY,
subtype text, -- Inbound/Outbound
name text,
company text, -- FREE TEXT (no account pre-convert)
email citext, phone text,
status text, -- stage lifecycle
source text, score int,
owner_id text REFERENCES dev_user(id),
is_converted bool DEFAULT false,
converted_account_id text REFERENCES account(id),
converted_contact_id text REFERENCES contact(id),
converted_opportunity_id text, -- set on convert
external_uid text UNIQUE);
CREATE TABLE opportunity (
id text PRIMARY KEY,
subtype text, -- New-Business/Renewal/Upsell
name text NOT NULL,
account_id text REFERENCES account(id),
primary_contact_id text REFERENCES contact(id),
amount money,
stage text NOT NULL, -- stage diagram, 1:1 map
pipeline text,
probability numeric(5,2),
close_date date, -- optionalTs on load
forecast_category text,
owner_id text REFERENCES dev_user(id),
external_uid text UNIQUE,
created_at timestamptz, updated_at timestamptz);
CREATE TABLE product ( -- sales catalog, NOT DevRev Part
id text PRIMARY KEY,
subtype text, -- Good/Service/Subscription
name text NOT NULL,
sku text,
unit_price money,
family text, is_active bool DEFAULT true,
external_uid text UNIQUE);
CREATE TABLE line_item ( -- opp/quote/order child
id text PRIMARY KEY,
parent_type object_kind, parent_id text, -- opp/quote/order
product_id text REFERENCES product(id),
quantity numeric, unit_price money, total money);
CREATE TABLE activity (
id text PRIMARY KEY,
type text NOT NULL, -- task|call|meeting|email|note
subtype text, -- demo/discovery/follow-up
title text,
body text, -- rich_text
status text, -- open/in_progress/completed
due_date timestamptz,
owner_id text REFERENCES dev_user(id),
-- call-specific
direction text, duration_sec int, disposition text,
recording_artifact_id text,
external_ref text UNIQUE,
created_at timestamptz, updated_at timestamptz);
-- ★ polymorphic link: one activity → many objects (WhoId/WhatId analog)
CREATE TABLE activity_link (
activity_id text REFERENCES activity(id) ON DELETE CASCADE,
target_type object_kind NOT NULL, -- account/contact/lead/opp/ticket/issue…
target_id text NOT NULL,
role text, -- who vs what; decision-maker etc
PRIMARY KEY(activity_id, target_type, target_id));
CREATE INDEX ON activity_link (target_type, target_id); -- "all activity on X"
CREATE TABLE template (
id text PRIMARY KEY,
subtype channel NOT NULL, -- email|sms|whatsapp|voice
name text NOT NULL,
provider text, -- Twilio/MSG91/Gupshup/Meta…
profile_id text, -- sender/WABA/number profile
language text,
body text, -- text/HTML
variables jsonb, -- merge tokens
approval_status approval_status DEFAULT 'draft', -- synced from provider
external_template_id text, -- provider id = sync key
-- channel-differentiated (nullable per subtype)
subject text, -- email
from_addr text, reply_to text, -- email
hsm_category text, -- whatsapp: MARKETING/UTILITY/AUTH
components jsonb, -- whatsapp: header/footer/buttons
quality_rating text, -- whatsapp: green/yellow/red
dlt_template_id text, dlt_entity_id text, sender_id text, -- sms India
us_campaign_id text, -- sms 10DLC
ivr_flow jsonb); -- voice
CREATE TABLE channel_profile (
id text PRIMARY KEY,
channel channel NOT NULL,
provider text NOT NULL,
sender_identity text, -- number / short code / WABA / from-email
keyring_id text NOT NULL, -- creds in developer_keyrings, NOT here
brand_id text REFERENCES brand(id),
team_id text REFERENCES team(id),
is_active bool DEFAULT true);
-- message/call log rows reference channel_profile + link via activity_link
schemas.custom.set; each column = a custom_schema_fragment field of the DevRev type in the map above. (2) external_ref/external_uid UNIQUE → idempotent AirSync upserts; native identity objects (account/contact/devuser) map to stock Rev/Dev objects, the rest are custom leaf types. (3) money composite degrades to int minor-units + currency enum until native money exists. (4) activity_link = the polymorphic workaround (join leaf); index (target_type,target_id) powers "all activity on this account". (5) Secrets in developer_keyrings, referenced by id — never in channel_profile. (6) Address: flatten to fields for filtering (jsonb shown for brevity). (7) subtype = record-type on every object. This loads over AirSync as-is; the two STD-NEEDED primitives would remove the workarounds.