Sync Salesforce leads to message.com
Push Salesforce Lead and Contact records into message.com visitor records. Status, source, owner, and any custom field. The inbox 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];
HttpRequest req = new HttpRequest();
req.setEndpoint('https://app.message.com/api/v1/visitors/upsert');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + System.Label.MessageApiKey);
req.setHeader('Content-Type', 'application/json');
req.setHeader('Idempotency-Key', leadId);
Map<String, Object> body = new Map<String, Object>{
'email' => lead.Email,
'name' => (lead.FirstName + ' ' + lead.LastName).trim(),
'externalId' => 'salesforce:' + lead.Id,
'attributes' => new Map<String, Object>{
'leadStatus' => lead.Status,
'leadSource' => lead.LeadSource,
'ownerId' => String.valueOf(lead.OwnerId)
}
};
req.setBody(JSON.serialize(body));
new Http().send(req);
}
}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 visitor in
app.message.com. ConfirmleadStatus: Qualified. - 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. Upsert by external ID; backfill email when it appears.