The Google integration covers four surfaces: OAuth-based identity (sign in with Google), Gmail for both inbound mail sync and outbound sending, Google Ads management through the Marketing module, and YouTube engagement sync. One Google connection can grant any subset of these via OAuth scopes.
Connect Google
Google uses OAuth 2.0 with offline access for refresh tokens. The vendor segment in the universal flow is google.
const { authUrl } = await fetch('/api/upstream/call/google-provider/get-auth-url', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
scopes: [
'openid',
'email',
'profile',
'https://www.googleapis.com/auth/gmail.send',
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/adwords',
'https://www.googleapis.com/auth/youtube.readonly',
],
returnUrl: 'https://admin.example/integrations',
accessType: 'offline',
prompt: 'consent',
}),
}).then(r => r.json());
window.location = authUrl;
accessType: 'offline' is required to get a refresh token; without it, calls work for the first hour and then start failing. prompt: 'consent' forces Google to re-show the consent screen on reconnect, which is necessary if scopes change.
Identity (sign in with Google)
The standard "Sign in with Google" flow uses minimal scopes (openid email profile). It's a separate connector path from the full Google integration:
/profile/googleNo auth/profile/google/redirectNo authThese come from the Users module, not Upstream — see the authentication docs for the customer-facing sign-in flow. The Upstream Google connector is for admin/operator workflows where the platform needs ongoing access to Gmail or Google Ads on the org's behalf.
Gmail
Sending
await fetch('/api/upstream/call/google-provider/gmail-send', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
from: '[email protected]',
to: '[email protected]',
subject: 'Following up',
html: '<p>Just checking in...</p>',
text: 'Just checking in...',
threadId: 'gmail-thread-id-optional',
}),
});
Sent through the connected user's Gmail account, so the message appears in their Sent folder and replies go back to their inbox. Used for sales-rep outreach where the email needs to look personal, not transactional.
Inbound sync
The Sync module's external mail job polls connected Gmail accounts, ingests new threads, and creates CRM activities for matching contacts. Configuration on the Sync side picks which labels to sync.
// trigger an immediate sync (instead of waiting for the scheduled run)
await fetch('/api/sync/trigger', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({ platform: 'google', syncType: 'gmail' }),
});
Google Ads
The CRM ads module wires through the Google connector to push campaign create, update, pause, and metrics calls. Application code uses the Ads module — it knows how to translate the unified ads campaign shape into Google Ads' resource structure.
The adwords scope is what unlocks the Google Ads API; business_management does nothing on Google (that's Facebook).
Conversion tracking
Google Ads conversions can flow back into the CRM via webhook:
/connect/webhook/googleNo authThe connector handles webhook actions:
youtube-webhook→ engagement events on YouTube videosyoutube-verify→ YouTube subscription verification challengeads-webhook→ general ads eventads-conversion-callback→ a Google Ads conversion firedads-performance-alert→ Google Ads performance alert
Conversion callbacks let you tie an ads click to a downstream conversion event in your CRM (form fill, purchase). Set the conversion endpoint in Google Ads to https://api.appmint.io/connect/webhook/google?action=ads-conversion-callback&orgid=<your-org>.
YouTube
If youtube.readonly scope is granted, the YouTube sync job pulls comment and engagement metrics on connected channels. Useful for orgs that run their own YouTube presence — comments become CRM inbox messages.
YouTube live-streaming events can also push notifications via the PubSubHubbub protocol; the connector handles the subscription verification at youtube-verify action.
Refresh and expiry
Refresh tokens last indefinitely unless revoked, the user changes their password, or the token isn't used for 6 months. The connector refreshes access tokens automatically when they expire (every hour). On refresh failure, the integration moves to status: "expired" and the admin is notified.
If a user revokes access in their Google Account → Security → Third-party apps panel, the next call returns a 401 from Google. The connector marks the integration expired immediately.
App verification
Google requires app verification for sensitive scopes (Gmail, Drive, YouTube content). Until verified, the OAuth screen shows an "unverified app" warning and limits to 100 users. Run the verification process in the Google Cloud Console; it takes 4-6 weeks and requires submitting a privacy policy, demo video, and security questionnaire.
For internal-only workspaces (G Suite), you can mark the OAuth client as Internal which skips verification but limits connections to your own domain.
Common quirks
- Scope creep — adding scopes after initial connect doesn't update the existing token. Re-run the auth flow with
prompt: 'consent'to re-grant. - Per-user vs domain-wide — for G Suite admins, domain-wide delegation lets the platform act on any user's behalf without each user connecting individually. This requires service account keys, not OAuth — store the JSON key in
credentials.serviceAccountKey. - Quota — Gmail API has per-user and per-project quotas. The platform respects them; bulk operations should be paced.
Sign-in-with-Google for end customers (storefront, customer portal) goes through the Users module's OAuth strategy, not the Upstream Google connector. The two share Google as a vendor but solve different problems.