Sending from your Google account
Send campaigns and transactional email from Gmail or Google Workspace — the one-click OAuth connection, the app-password shortcut, and how to sign in to Growth with Google.
Growth can send through your ordinary Google mailbox — no domain to verify, no Amazon SES account. There are two ways in, and one of them works right now with no configuration at all.
Which route to take
| App password | Connect Google (OAuth) | |
|---|---|---|
| Setup | 2 minutes, inside your Google account | One-time Google Cloud project |
| What Growth stores | An app password | A send-only authorization you can revoke |
| Read access to your mail | None | None |
| Revoking | Delete the app password | One click in your Google account |
| Where | Custom SMTP provider | Connect Google button |
Start with the app password if you want to send today. Move to the OAuth connection when you want a revocable grant and no password in play.
Sending limits — read this first
Google counts recipients, not messages, across To, Cc and Bcc:
- 500 recipients per day on a personal
@gmail.comaccount - 2,000 recipients per day on Google Workspace
Past the cap Google rejects the rest of the day's mail. A 3,000-contact campaign will not go out from Gmail — use Resend, Brevo or Amazon SES for lists that size, and keep Gmail for transactional mail, support replies and small segments.
Route 1 — app password (no configuration)
Turn on 2-step verification at myaccount.google.com/security. Google will not offer app passwords without it.
Create an app password at myaccount.google.com/apppasswords. Copy the 16-character value; Google never shows it again.
In Growth, go to Email marketing → Settings → Sending → New application and choose Custom SMTP.
Field Value SMTP host smtp.gmail.comPort 465Implicit TLS on Username your full Google address Password the 16-character app password (no spaces) From email the same Google address Save, then press Test.
Gmail rejects a From that is not your own address, so keep the From email and
the SMTP username identical.
Route 2 — connect your Google account
This is the Connect Google button on the sending settings page. It needs a Google Cloud OAuth client once, then every organization connects in one click.
Step 1 — Google Cloud project
- Open console.cloud.google.com and create a project (or reuse one).
- APIs & Services → Library → Gmail API → Enable. Without this the connection succeeds and every send fails.
Step 2 — OAuth consent screen
Add exactly three scopes — openid, email, and
https://www.googleapis.com/auth/gmail.send.
gmail.send lets an app send as you and grants no ability to read, modify
or delete anything in the mailbox. Google classifies it as sensitive, not
restricted: publishing needs a brand review, not the paid annual CASA
security assessment that full-mailbox scopes require.
Then pick your audience:
Google Workspace on your own domain → choose "Internal". No verification, ever. No 100-user cap. No token expiry. Stop here.
Personal
@gmail.com→ choose "External", then press "Publish app".Publishing matters more than it looks. While the app sits in Testing, Google expires every refresh token after 7 days — your campaigns would break every week. Published but unverified is fine: users see an "unverified app" warning they can click through, and you are capped at 100 connected accounts. Submit for verification when you open this to customers.
Step 3 — OAuth client
APIs & Services → Credentials → Create credentials → OAuth client ID → Web application. The two flows live on different hosts, which is the single easiest thing to get wrong here:
| Flow | Redirect URI | Host |
|---|---|---|
| Gmail connection | https://<convex-site>/google/gmail/callback | Convex site (*.convex.site) |
| Google sign-in | https://<app-domain>/api/auth/callback/google | your app domain |
The Gmail callback is a Convex httpAction, so it answers on the deployment's
.convex.site URL. Sign-in is Better Auth, which this app serves through
src/routes/api/auth/$.ts on its own domain and only proxies to Convex — so
its redirect URI carries the app origin, never the Convex one.
Add every origin you sign in from. A Web-application client matches redirect
URIs exactly, port included, so http://localhost:3000 and
http://localhost:3058 are two separate entries — register the ports you
actually run pnpm start-all on.
Step 4 — store the keys in Convex
npx convex env set GOOGLE_CLIENT_ID "<client-id>"
npx convex env set GOOGLE_CLIENT_SECRET "<client-secret>"These serve both Google sign-in and Gmail sending. To isolate them — so a pending Gmail verification cannot affect sign-in — create a second OAuth client and set it separately; Growth falls back to the pair above when these are absent:
npx convex env set GMAIL_OAUTH_CLIENT_ID "<gmail-client-id>"
npx convex env set GMAIL_OAUTH_CLIENT_SECRET "<gmail-client-secret>"Step 5 — connect
Email marketing → Settings → Sending → Connect Google. Pick the account, accept the consent screen, and you land back on the settings page with the connection created and set as default. Press Test to confirm: unlike an API key, an OAuth grant can be revoked from the Google side, so the test actually mints a token rather than checking the stored value's shape.
The sender address is fixed to the connected account — Gmail accepts no other
From. You can still set a display name and a reply-to.
Signing in to Growth with Google
Nothing to build: once GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set in
Convex, the Sign in with Google button appears on /auth/signin by itself.
It needs the https://<app-domain>/api/auth/callback/google redirect URI from
step 3 — the app domain, not the Convex one.
Troubleshooting
"Google did not return a refresh token." The account already authorized Growth, so Google skipped the consent screen. Remove Growth at myaccount.google.com/permissions and connect again.
Sends fail with "The Google account is no longer authorized". The grant was revoked, or the OAuth app is still in Testing and the refresh token aged out after 7 days. Publish the app, then use Reconnect the Google account.
"Request had insufficient authentication scopes." The consent screen is
missing gmail.send. Add it, then reconnect — existing tokens do not gain
scopes retroactively.
Everything works, then stops mid-campaign. You hit the daily recipient cap. It resets on a rolling 24-hour basis.
Open and click tracking
Gmail sends no delivery webhooks, so Growth tracks opens and clicks itself with its own pixel and link redirection — the same mechanism used for Resend, Brevo and SMTP. Bounces are not reported back, which is the real cost of sending from a mailbox rather than an ESP: watch your list hygiene manually.