message.comDevelopers

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

  1. In Salesforce Setup, open Remote Site Settings.
  2. 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.com and 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

  1. Deploy to a sandbox via Setup → Deployments, or push from VS Code with the SFDX CLI.
  2. Edit a Lead. Change Status to Qualified.
  3. Open the matching visitor in app.message.com. Confirm leadStatus: Qualified.
  4. 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 HttpCalloutMock in 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.

Next steps