Most Salesforce email integrations only run one direction: they scrape the inbox and drop activity into the CRM. A two-way sync also pushes the other way. When a rep logs a follow-up in Salesforce or moves an Opportunity stage, that action turns into a real sent message or a calendar event on the rep’s Gmail or Outlook account. This recipe wires both directions together with change-driven webhooks so the two systems stay aligned without a polling loop hammering either side every few minutes.
If you only need the inbound half (email landing in Salesforce as Tasks), use Export email data to Salesforce and stop there. This page assumes that mapping already exists and focuses on the second direction plus the plumbing that keeps both directions consistent.
What does two-way Salesforce sync mean?
Section titled “What does two-way Salesforce sync mean?”Two-way sync means changes flow both ways: email and calendar activity from Gmail or Outlook lands in Salesforce, and actions taken inside Salesforce produce real messages or events back on the connected account. One direction alone leaves a gap that reps fill by hand.
The inbound direction is covered by the existing export recipe, so treat it as a given here. The outbound direction is where the work is. A rep clicks “Log and send” on a Salesforce Task, and your integration calls Nylas to actually send that message from their mailbox. A closed-won Opportunity creates a kickoff event on the customer’s calendar. Across a 50-user sales team, outbound actions typically run 300 to 800 events per day, which is light traffic but demands ordering guarantees so a later edit never loses to an earlier one. The two halves share one identifier map, described below, so a record written by one direction is never re-imported by the other.
How do you capture email activity into Salesforce?
Section titled “How do you capture email activity into Salesforce?”Inbound capture pulls new messages and calendar events from the connected grant and writes them to Salesforce as Tasks and Events. You read from Nylas, map each item to the Salesforce object model, then upsert through the Salesforce Composite or Bulk API on a schedule or in response to a webhook.
The read side uses two Nylas endpoints. GET /v3/grants/{grant_id}/messages returns email, and GET /v3/grants/{grant_id}/events?calendar_id=<CALENDAR_ID> returns calendar items, both filterable by a time window so each run only fetches what newly arrived. A typical incremental run over 5 minutes of activity pulls fewer than 40 messages per grant, which keeps you well under provider rate limits. For the field-by-field Task mapping (sender to Contact, domain to Account, subject to Task), follow Export email data to Salesforce rather than repeating it here.
# Pull messages received in the last sync windowcurl -X GET "https://api.us.nylas.com/v3/grants/GRANT_ID/messages?received_after=1718351400&limit=50" \ -H "Authorization: Bearer NYLAS_API_KEY"Calendar capture works the same way against the events endpoint, and both feeds funnel into the same Salesforce write path.
How do you push Salesforce changes back to email and calendar?
Section titled “How do you push Salesforce changes back to email and calendar?”The outbound direction listens for Salesforce changes (via a Platform Event, Change Data Capture stream, or outbound message) and translates each one into a Nylas call. A logged email becomes a send request, and a scheduled meeting becomes a calendar event on the rep’s connected account.
Two Nylas endpoints carry the outbound traffic. POST /v3/grants/{grant_id}/messages/send sends a message from the rep’s mailbox, and POST /v3/grants/{grant_id}/events?calendar_id=<CALENDAR_ID> creates a calendar event. Each call should complete in roughly 400 to 900 ms depending on the provider, so a queue worker can clear hundreds of actions per minute. Always tag the outbound payload with the Salesforce record ID so the inbound direction recognizes the result as its own and skips re-importing it.
import requests
# A Salesforce "log and send" action becomes a real sent messageresp = requests.post( "https://api.us.nylas.com/v3/grants/GRANT_ID/messages/send", headers={"Authorization": "Bearer NYLAS_API_KEY"}, json={ "subject": "Following up on Q2 plan", "body": "Thanks for the call earlier...", # Tracking label tags the send so inbound sync recognizes it "tracking_options": {"label": "sf-task-00T5g000004abcd"}, },)resp.raise_for_status()Store the returned message ID against the Salesforce Task so a later edit updates the same record instead of creating a duplicate.
Which webhooks drive the sync?
Section titled “Which webhooks drive the sync?”Webhooks replace polling so each direction reacts only to real changes. Nylas notifications cover the email and calendar side, and Salesforce Change Data Capture covers the CRM side. Subscribing to both means neither system needs a timer loop checking for updates every few minutes.
On the Nylas side, subscribe to event.created, event.updated, and event.deleted for calendar changes, plus grant.expired so a broken connection surfaces immediately instead of failing silently for hours. Those 4 trigger types cover every change the outbound direction needs to react to. One important caveat: message.send_success only fires for scheduled sends, not immediate sends, so do not rely on it to confirm a normal outbound message landed. Confirm those by checking the response from the send call instead. A healthy deployment of 50 users generates somewhere around 2,000 to 6,000 webhook deliveries per day across all trigger types. See Notifications for the full payload reference and signature verification.
# Register a webhook for calendar changes and grant healthcurl -X POST "https://api.us.nylas.com/v3/webhooks" \ -H "Authorization: Bearer NYLAS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "trigger_types": ["event.created", "event.updated", "event.deleted", "grant.expired"], "webhook_url": "https://yourapp.example.com/nylas/webhook", "description": "Salesforce two-way sync" }'Each incoming webhook enqueues a single sync job keyed by the changed record, which is what keeps the two directions from racing.
How do you handle sync conflicts?
Section titled “How do you handle sync conflicts?”A conflict happens when both systems change the same record before either change syncs. The fix is a deterministic rule applied to every record: pick a winner by timestamp, treat one system as authoritative per field, and make every write idempotent so replays are safe.
Three rules cover almost every case. First, last-write-wins by the source system’s modified timestamp resolves simple edit races, and in practice fewer than 2% of records ever hit a true conflict. Second, assign field ownership: let Salesforce own deal fields and let the calendar own meeting times, so the two systems rarely fight over the same attribute. Third, make writes idempotent by keying on the shared ID map (Nylas message or event ID on one side, Salesforce record ID on the other) so a redelivered webhook never creates a duplicate. The event endpoints include updated timestamps you can compare directly. For the inbound contact and Task mapping that feeds this map, reuse Export email data to Salesforce.
def resolve(local, remote): # Last-write-wins on the source modified time; idempotent by shared id if remote["updated_at"] > local["updated_at"]: return remote return localThings to know
Section titled “Things to know”- Send confirmation.
message.send_successis scheduled-send only. For immediate sends, trust the HTTP response from the send call, not a webhook. - Grant expiry. Subscribe to
grant.expiredand surface re-authentication to the rep fast. A dead grant silently stops both directions, and a stale CRM erodes trust within days. - Loop prevention. Tag every outbound write with its Salesforce source ID and skip those on the inbound pass. Without this guard a single logged email can ping-pong between systems forever.
- Rate limits. Batch Salesforce writes through the Composite API and keep Nylas reads windowed. A 50-user team rarely exceeds 5,000 requests per day to the API in steady state.
Next steps
Section titled “Next steps”- Export email data to Salesforce: the inbound Task mapping this page builds on
- Sync calendar events to a CRM: calendar-specific capture detail
- Notifications: webhook payloads, triggers, and signature verification