The files your customers actually send
A new customer migrating to your CRM shows up with three spreadsheets. The first is an export from the tool they are leaving, with column names like ContactFullName__c and a Stage column whose values are the previous vendor's internal IDs. The second is a shared Google Sheet that the sales team has edited by hand for years: a Name column holding "Dr. Priya Raman, MBA", a Phone column with 415.555.0132 next to +1 (415) 555-0199 ext 4, and a Source column that contains "Webinar", "webinar ", "Wbinar", and "Referral from Dan". The third is a conference badge scan with no headers at all.
Dates arrive as 03/04/2024 and 4 Mar 2024 in the same column, and nobody knows whether the first one is March or April. Owner is a person's first name, sometimes a nickname, occasionally someone who left the company. Emails are missing a TLD or have two people separated by a slash. The CRM team's job is to get all of it into accounts, contacts, and deals without their success managers spending a week in VLOOKUP.
Fields and rules that matter in CRM
| Field | What goes wrong in real files | How Dromo catches it |
|---|---|---|
Typos, missing @, duplicate rows for the same person |
Email type validates format; unique validator flags duplicates before they become duplicate contacts |
|
| Phone | Parentheses, dots, extensions, and international prefixes mixed together | Column hook normalizes every value to E.164 before validation |
| First and last name | One Full Name column with titles and suffixes |
Row hook splits the value into first_name and last_name and strips honorifics |
| Company | Trailing whitespace and "Inc." versus "Inc" | Column hook trims and standardizes suffixes so accounts dedupe cleanly |
| Lead source | Free text where an enum was intended | Select field with allowed values; the bulk transform step maps stragglers to the nearest option |
| Owner | Names of people who are not users, or who left | Custom validator calls your user API and rejects unknown owners with a clear message |
| Stage | Vendor-specific labels that do not match your pipeline | Allowed values, with the mapping remembered for that customer's next import |
| Created date | Mixed formats and ambiguous day/month order | Date parsing configured per import, with an explicit output format |
A working CRM schema
{
"fields": [
{
"label": "Email",
"key": "email",
"type": "email",
"validators": [{ "validate": "required" }, { "validate": "unique" }]
},
{
"label": "Phone",
"key": "phone",
"type": "string",
"validators": [{ "validate": "regex_match", "regex": "^\\+[1-9]\\d{6,14}$", "errorMessage": "Phone must be in E.164 format" }]
},
{
"label": "Company",
"key": "company",
"type": "string",
"validators": [{ "validate": "required" }]
},
{
"label": "Lead source",
"key": "lead_source",
"type": "select",
"selectOptions": [
{ "label": "Webinar", "value": "webinar" },
{ "label": "Referral", "value": "referral" },
{ "label": "Inbound", "value": "inbound" },
{ "label": "Outbound", "value": "outbound" }
]
},
{
"label": "Stage",
"key": "stage",
"type": "select",
"selectOptions": [
{ "label": "Lead", "value": "lead" },
{ "label": "Qualified", "value": "qualified" },
{ "label": "Proposal", "value": "proposal" },
{ "label": "Closed Won", "value": "closed_won" }
]
},
{
"label": "Created date",
"key": "created_at",
"type": ["date", { "outputFormat": "YYYY-MM-DD" }]
}
]
}
The phone regex runs after a column hook has normalized the raw values, and the owner check lives in a custom validator that queries your user list, so neither appears in the static schema. A PM can assemble this exact configuration in Schema Studio by adding fields, picking validators from a menu, and typing the allowed values, without opening an editor.
Security and deployment for CRM
Contact data is commercially sensitive but rarely regulated the way health or payroll records are, so CRM teams tend to optimize for time to value. The embedded widget is the default path: drop it into your app, point it at the schema, and start migrating customers this week. SOC 2 Type II certification is in place and a security report is available under MNDA through the Trust Center, which is usually enough to clear a buyer's vendor review.
Two options matter when a prospect asks harder questions. Private Mode keeps the entire import in the browser with zero retention, which satisfies customers who do not want their pipeline on a third party's servers. Bring Your Own Storage writes results straight from the browser to your own S3, GCS, Azure Blob, or Dropbox location, so your infrastructure is the only place the data lands.
Connect it to Salesforce, HubSpot, and your own API
For an embedded import, the onResults callback delivers the validated rows as JSON. From there, your backend can call the Salesforce Bulk API, the HubSpot batch endpoints, or your own contacts service, with the confidence that every email is well-formed, every owner exists, and every stage is one your pipeline recognizes.
If you would rather not route results through the browser, configure a webhook. Dromo POSTs the completed import to your endpoint with import metadata, and you can fetch the full payload or a presigned download from the REST API. For migrations run by your services team rather than end users, the Headless API accepts the same schema and processes files programmatically, producing a fix-it URL only when a row needs a human decision.

