Salesforce
The native Salesforce integration puts iMessage threads on Contact and Lead timelines with an embedded reply panel; the Flow build below covers record-triggered sends.
The native integration
Salesforce does not need the Flow build below to see your messages. Connected natively, Blue Reacher writes iMessage activity onto Salesforce records and lets reps answer without leaving them:
- Contact and Lead history. Each conversation lands on the matching Contact or Lead timeline as it happens.
- Embedded replies. An embedded panel on the record shows the thread and sends replies as real iMessages from your line.
- Contact sync. Records stay matched by phone number.
Done-for-you setups get this connected during onboarding. The Flow build below is for the other direction: firing sends from record-triggered automation.
The Flow path: record-triggered sends
A record-triggered Flow sends an iMessage on any Lead or Contact condition. Replies come back as completed Tasks on the matching record, with a field your reps can filter and report on.
Step 1: store the key properly
Never put the API key in a Flow. Use Named Credentials.
- Setup, Named Credentials, External Credentials, New.
- Authentication Protocol: Custom.
- Add a Principal, then under Authentication Parameters add name
ApiKey, valueBearer brk_your_api_key. - Create a Named Credential pointing at
https://api.bluereacher.com, linked to that External Credential. - Under the External Credential's Custom Headers, add
Authorizationwith value{!$Credential.BlueReacher.ApiKey}. - Give the running user's permission set access to the External Credential Principal.
Step 2: outbound from a Flow
Create a record-triggered Flow on Lead or Contact.
- Trigger: when the record meets your condition, for example Status becomes Working or a custom Ready for Outreach checkbox is set.
- Add an HTTP Callout action against the Named Credential.
- Method POST, path
/v1/messages. - Body:
{
"to": "{!$Record.MobilePhone}",
"message": "Hi {!$Record.FirstName}, it's Marcus from Northside. You asked about pricing last week, want me to send it over?",
"metadata": { "crm_id": "{!$Record.Id}" }
}Use MobilePhone, not Phone. Phone is frequently a landline or a switchboard, and sending to it is a wasted send at best.
Salesforce phone fields are rarely E.164. Add a formula field that normalizes to +1XXXXXXXXXX for US numbers and map the callout to that field rather than the raw one.
Step 3: inbound as Tasks
Expose an Apex REST endpoint and register its URL as your webhook endpoint.
@RestResource(urlMapping='/bluereacher/*')
global with sharing class BlueReacherWebhook {
@HttpPost
global static void handleReply() {
RestRequest req = RestContext.request;
String raw = req.requestBody.toString();
Blob mac = Crypto.generateMac(
'hmacSHA256',
Blob.valueOf(raw),
Blob.valueOf(BlueReacherSettings__c.getInstance().WebhookSecret__c)
);
String expected = 'sha256=' + EncodingUtil.convertToHex(mac);
String provided = req.headers.get('x-bluereacher-signature');
if (provided == null || !ConstantTimeCompare.equals(expected, provided)) {
RestContext.response.statusCode = 401;
return;
}
Map<String, Object> e = (Map<String, Object>) JSON.deserializeUntyped(raw);
if (e.get('event') != 'message.received') {
RestContext.response.statusCode = 200;
return;
}
Map<String, Object> data = (Map<String, Object>) e.get('data');
// Resolve the record by the normalized phone number.
String phone = (String) data.get('phone_number');
Contact c = [SELECT Id FROM Contact WHERE MobilePhone_E164__c = :phone LIMIT 1];
Task t = new Task(
WhoId = c.Id,
Subject = 'iMessage reply',
Description = (String) data.get('content'),
Status = 'Completed',
ActivityDate = Date.today()
);
insert t;
RestContext.response.statusCode = 200;
}
}Store the signing secret in a protected custom setting or custom metadata, not in the class. Expose the endpoint through a Connected App with OAuth, or a Site with the right guest-user permissions, and restrict it to the minimum needed to insert a Task.
WhoId accepts both Lead and Contact IDs, so a single handler covers both as long as your Flow always sends {!$Record.Id}.
Governor limits
A reply burst means many small transactions rather than one large one, so the usual bulk-DML advice does not apply here. What does: keep the Apex handler to a single insert, do no SOQL you can avoid, and never call out to another system from inside the webhook handler. If you need enrichment, fire a Platform Event and process it asynchronously.
Opt-out sync
Subscribe to contact.opted_out and set HasOptedOutOfEmail-style custom field, for example iMessage_Opted_Out__c, on the matching record. Add it as a filter on every outbound Flow. Details in opt-out handling.
Checklist
- Key in a Named Credential, not in the Flow
-
MobilePhoneused, normalized to E.164 by formula field -
metadata.crm_idmapped to{!$Record.Id}, and an indexed E.164 phone field to resolve replies - Signature verified with a constant-time comparison
- Endpoint permissions limited to Task insert
- Opt-out field syncing and filtered on every Flow
HubSpot
The native HubSpot integration puts iMessage threads in the conversations inbox with two-way replies and contact sync; the API path below covers workflow-triggered sends.
Close
Native Close integration: every iMessage conversation lands on the right lead's activity feed, matched by phone, with optional lead creation for unknown numbers.

