Salesforce
MailInApp doesn't have a Salesforce app to install — you don't need one. Salesforce's own REST API answers plain JSON over HTTPS, and MailInApp's API data source with OAuth2 JWT Bearer authentication is built to speak Salesforce's server-to-server login flow directly: no interactive consent screen, no refresh token to babysit, and your Salesforce password never touches MailInApp at all.
This guide sets up a read-only connection — pulling Opportunity, Account, or Contact rows into merge tags, repeat blocks, and live-bound chart blocks. It doesn't write back to Salesforce.
How the connection works
- MailInApp signs a short-lived JWT assertion with your Salesforce Connected App's private key.
- It POSTs that assertion to Salesforce's token endpoint, which verifies the signature against the certificate you uploaded and returns an access token — no username or password sent, ever.
- MailInApp caches the access token and calls the Salesforce REST API (a SOQL query) with it as a
Bearertoken, refreshing automatically before the token expires or if a call ever comes back unauthorized.
Every step happens server-side, on each fetch — there's nothing to keep alive or re-authorize by hand once it's set up.
Set up the Salesforce side
- In Salesforce Setup, go to App Manager → New Connected App (or New Connected App (Lightning)).
- Fill in the basic name/email fields, then check Enable OAuth Settings.
- Under Use digital signatures, upload a certificate. If you don't already have one, generate a self-signed certificate and its matching RSA private key — the certificate goes to Salesforce, the private key goes to MailInApp. Keep the private key somewhere safe; you'll paste it into MailInApp once and it's masked afterward.
- Add the OAuth scope your integration needs —
api(Manage user data via APIs) is enough for reading records. - Save, then edit the Connected App's policies: set Permitted Users to Admin approved users are pre-authorized. This is what makes the flow non-interactive — without it, Salesforce expects a human to click through a consent screen, which a server-to-server JWT exchange can't do.
- Assign the integration user (a real Salesforce user with API access enabled on their profile, or a dedicated integration-only user) to the Connected App via a permission set.
- Note the Connected App's Consumer Key — that's the
issuerMailInApp needs — and your org's My Domain URL (Setup → My Domain), e.g.https://yourorg.my.salesforce.com.
Connect it in MailInApp
From Data Sources → + API connection, set:
- Endpoint — your org's REST query URL, e.g.
https://yourorg.my.salesforce.com/services/data/v61.0/query?q=SELECT+Name,Amount,StageName,CloseDate+FROM+Opportunity+WHERE+IsClosed+=+false(a URL-encoded SOQL query — see Querying with SOQL below). - Data path —
records. Salesforce's query response wraps the actual rows in arecordsarray alongsidetotalSize/donefields. - Authentication — OAuth2 JWT Bearer:
- Token URL —
https://login.salesforce.com/services/oauth2/tokenfor production and Developer Edition orgs,https://test.salesforce.com/services/oauth2/tokenfor a sandbox. - Issuer — the Connected App's Consumer Key.
- Subject — the integration user's Salesforce username (the identity the JWT asserts).
- Audience — the same host as the token URL:
https://login.salesforce.com(orhttps://test.salesforce.comfor a sandbox). - Private key — the RSA private key paired with the certificate you uploaded to the Connected App.
- Token URL —
Click Test these settings to confirm the connection before saving — it mints a real token and previews the first few rows. Once saved, use Test saved connection any time afterward to re-check it without re-pasting the private key.
Querying with SOQL
The endpoint's query string is the data you get back — there's no separate "pick your fields" UI, so shape the SOQL query to return exactly the columns your email needs:
SELECT Name, Amount, StageName, CloseDate FROM Opportunity WHERE IsClosed = false
A few things worth knowing about the shape of what comes back:
- Flat fields only. A relationship field like
Owner.EmailorAccount.Namecomes back as a nested JSON object ({"Owner": {"Email": "..."}}), and MailInApp's row parser drops nested values rather than guessing how to flatten them. If you need an owner or account identifier as a usable merge tag, select a flat field instead —OwnerId, for example — rather than a dotted relationship path. - Up to 1,000 rows per fetch. Plenty for a digest or dashboard email; narrow further with
WHERE/ORDER BY/LIMITin the query itself if you only want a specific slice. - Fetched fresh on every render. A recurring send re-runs the query and re-renders bound blocks with Salesforce's current rows on each send — there's no caching beyond the OAuth token itself.
Using the data
Once connected, add it to a project from the studio's Data panel with an alias (e.g. pipeline) and a role:
- Merge fields resolve
{{pipeline.field}}from the first returned row — useful for a single headline figure. - Repeat rows feed a repeat block — one row per Opportunity, rendered as a list or table.
- Collection bindings are what KPI scorecard, bar, line, and pie chart blocks bind to directly — a KPI scorecard's Value field set to
Amountwith a Sum aggregate gives you a live "total open pipeline" number with zero merge-tag plumbing, and a bar chart grouped byStageNameturns the same query into a pipeline-by-stage breakdown.
See Interactive Email Powered by Live Salesforce Data for a full worked example — a weekly pipeline digest built entirely from this connection.