Documentation menu

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

  1. MailInApp signs a short-lived JWT assertion with your Salesforce Connected App's private key.
  2. 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.
  3. MailInApp caches the access token and calls the Salesforce REST API (a SOQL query) with it as a Bearer token, 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

  1. In Salesforce Setup, go to App Manager → New Connected App (or New Connected App (Lightning)).
  2. Fill in the basic name/email fields, then check Enable OAuth Settings.
  3. 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.
  4. Add the OAuth scope your integration needs — api (Manage user data via APIs) is enough for reading records.
  5. 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.
  6. 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.
  7. Note the Connected App's Consumer Key — that's the issuer MailInApp 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 pathrecords. Salesforce's query response wraps the actual rows in a records array alongside totalSize/done fields.
  • AuthenticationOAuth2 JWT Bearer:
    • Token URLhttps://login.salesforce.com/services/oauth2/token for production and Developer Edition orgs, https://test.salesforce.com/services/oauth2/token for 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 (or https://test.salesforce.com for a sandbox).
    • Private key — the RSA private key paired with the certificate you uploaded to the Connected App.

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.Email or Account.Name comes 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/LIMIT in 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 Amount with a Sum aggregate gives you a live "total open pipeline" number with zero merge-tag plumbing, and a bar chart grouped by StageName turns 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.