The automation I built to stop losing data between Salesforce and the real world
Salesforce is, in theory, a single source of truth. In practice, at most nonprofits I’ve worked with, it’s a place where data goes to be aspirationally accurate.
The gap between what a CRM promises and what actually happens in the field is enormous. Staff are busy. Programs move fast. Forms get filled out, paper gets shuffled, and the ideal workflow, where every interaction is logged and every participant record is up to date, runs directly into the reality of a Tuesday morning when three grant reports are due and someone just called out sick.
I’ve spent over a decade in nonprofit operations, directing programs, managing grants, and building systems for organizations that are chronically understaffed. Most recently I’ve been working with a community food and agriculture nonprofit, and one of the central problems was program intake: getting hard copy applications and sign-in sheets from community members into Salesforce without requiring staff to manually re-enter everything.
The specific problem
The org runs four main programs across CSA shares, community gardens, youth employment, and educational workshops. Information came in through each program differently, usually as a paper sign-in sheet, application form, or Google Form, which a staff member would then manually review and enter into Salesforce. The problems with this:
- Data entry lag, sometimes days between application and CRM entry
- Data quality issues, with manual entry producing inconsistent field values, typos, and missing data
- No automatic follow-up, so staff had to remember to send confirmation emails
- No visibility, since until someone entered the data, the application didn’t exist in the system
What we tried first
The obvious first move was a native Zoho Forms to Salesforce connector, with the form writing directly to standard objects. That worked for Contact and Lead. It did not work for the Program Management Module (PMM) custom objects, specifically pmdm__ProgramEngagement__c, which is where program intake actually needs to land. The native connector couldn’t see PMM custom objects at all.
Second attempt: pipe Zoho through Zapier into Salesforce. Functional, but introduced a third vendor in the chain for something that should be a direct integration, and Zapier task pricing makes that a tax on every applicant forever.
What actually worked
The fix was to skip the native connector and use Zoho’s outbound webhook to hit a Salesforce REST API endpoint directly, with field mappings handled in Apex. The form posts JSON to a custom REST resource, the Apex class creates a Contact (or matches on email), creates the pmdm__ProgramEngagement__c record linked to the right program cohort, sets stage to Application Submitted, and triggers a confirmation email.
For applications that need review (edge cases, duplicate contacts, applications that need staff assignment), the form flags them and routes them to a Salesforce task queue rather than trying to handle them automatically. The automation handles the 80%, staff handles the 20% that actually needs human judgment.
What I’d do differently
Three things I learned that I’d apply to the next project:
Start with field-level security. Salesforce’s FLS rules caused more debugging time than anything else. Before building form integrations, map out every field the form needs to write to and confirm the integration user has the right permissions on every one. This sounds obvious. It isn’t, especially for fields the System Admin profile sees by default but the integration user doesn’t.
Build the error path first. The happy path (clean data, new contact, no duplicates) is easy. The error path is where real-world complexity lives. Before launching, think through what happens when someone applies twice, when a contact already exists with a different email, when a required field is blank.
Involve the staff who will actually use it. The best feedback I got on this system came from a program coordinator who pointed out several edge cases I hadn’t considered because she understood the program in ways I didn’t. Build with the people who know the work, not just the people who know the system.
Intake processing time went from 2-3 days to real-time. Staff time previously spent on manual data entry got redirected to actually supporting applicants. The data in Salesforce is now accurate enough to use for reporting.