Sync Salesforce leads to message.com
Push Salesforce Lead and Contact records into message.com Contact records. Status, source, owner, and any custom field. The customer directory sees who is hot, who is open, and who owns the deal.
Approach
Three patterns work. Pick one:
- Apex trigger + callout (recommended). Real-time on every lead change. Shown below.
- Outbound Message. No-code, fires on Process Builder / Flow. Sends a SOAP envelope to a listener you host.
- Scheduled batch. A nightly Apex job that exports lead changes since last run. Lowest fidelity, simplest.
1. Allow callouts to message.com
- In Salesforce Setup, open Remote Site Settings.
- Add a new entry. Remote Site URL:
https://app.message.com.
2. Store your API key
Two options:
- Custom Label. Setup → Custom Labels → New. Name
MessageApiKey. Paste the value. - Named Credential. Better for production. Set the endpoint to
https://app.message.comand configure header-based auth.
3. Write the trigger and queueable callout
LeadSyncToMessage.trigger
// Apex trigger that fires an Outbound Message on Lead changes
trigger LeadSyncToMessage on Lead (after insert, after update) {
for (Lead lead : Trigger.new) {
// Build the payload and HTTP POST asynchronously
System.enqueueJob(new MessageComUpsert(lead.Id));
}
}
public class MessageComUpsert implements Queueable, Database.AllowsCallouts {
private Id leadId;
public MessageComUpsert(Id leadId) { this.leadId = leadId; }
public void execute(QueueableContext ctx) {
Lead lead = [SELECT Id, Email, FirstName, LastName, Status, LeadSource, OwnerId
FROM Lead WHERE Id = :leadId];
// There is no upsert endpoint. Search by email first (the only real
// lookup key on message.com Contacts), then PATCH if found or POST if
// not. Shown here as the create branch; add the search-then-branch
// logic the same way the HubSpot tutorial does in JavaScript.
HttpRequest req = new HttpRequest();
req.setEndpoint('https://app.message.com/api/v1/contacts');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + System.Label.MessageWorkspaceJwt);
req.setHeader('Content-Type', 'application/json');
Map<String, Object> body = new Map<String, Object>{
'email' => lead.Email,
'name' => (lead.FirstName + ' ' + lead.LastName).trim()
};
req.setBody(JSON.serialize(body));
HttpResponse createRes = new Http().send(req);
String contactId = (String) ((Map<String, Object>) ((Map<String, Object>) JSON.deserializeUntyped(createRes.getBody())).get('contact')).get('id');
// Custom fields (Salesforce-specific data) go on a follow-up PATCH.
HttpRequest patchReq = new HttpRequest();
patchReq.setEndpoint('https://app.message.com/api/v1/contacts/' + contactId);
patchReq.setMethod('PATCH');
patchReq.setHeader('Authorization', 'Bearer ' + System.Label.MessageWorkspaceJwt);
patchReq.setHeader('Content-Type', 'application/json');
patchReq.setBody(JSON.serialize(new Map<String, Object>{
'customFields' => new Map<String, Object>{
'salesforceLeadId' => lead.Id,
'leadStatus' => lead.Status,
'leadSource' => lead.LeadSource,
'ownerId' => String.valueOf(lead.OwnerId)
}
}));
new Http().send(patchReq);
}
}Triggers run inside Salesforce's synchronous transaction limits. HTTP callouts must happen in a Queueable or @future async context, not directly inside the trigger.
4. Repeat for Contact
The same pattern applies to Contact, Opportunity, and any other object. Build separate triggers, or one generic dispatcher.
5. Test in a sandbox
- Deploy to a sandbox via Setup → Deployments, or push from VS Code with the SFDX CLI.
- Edit a Lead. Change Status to
Qualified. - Open the matching contact in
app.message.com. ConfirmleadStatus: Qualifiedin its custom fields. - Run your Apex tests. Salesforce requires 75% trigger coverage to deploy to production.
Common pitfalls
- Governor limits. A single trigger context can process 200 records. Batch your callouts; do not fire one HTTP call per record without queueing.
- Test classes for callouts. Salesforce requires
HttpCalloutMockin tests. Without it, callout-touching code cannot be deployed to production. - Lead converted. When a Lead converts to a Contact, the Lead ID changes. Listen for the conversion and re-key under the new ID.
- Email-less leads. Web-to-Lead forms can create leads without an email. Since email is the only real lookup key on Contacts, queue those and create the contact once an email shows up on a later update.