Demo mode. Sample data, Gmail sends are dry runs (the exact API request is recorded, nothing leaves), no login. The sample data resets once a day, or now with the button.

Gmail and Google Cloud setup

The application sends through the Gmail API as your dedicated Gmail account, using OAuth. It asks for one Gmail permission, gmail.send (send only; it cannot read the mailbox), plus the account's email address. Everything below happens in your own Google Cloud project and your own Gmail account; the developer is never involved after handoff.

Sources: Google's Gmail API sending guide, the Python quickstart, and the OAuth consent documentation (links at the end).

1. Create a Google Cloud project

  1. Go to https://console.cloud.google.com and create a project (any name, for example "Outreach").
  2. APIs and Services, Library: search "Gmail API" and click Enable. The Gmail API has no charge.

APIs and Services, OAuth consent screen (Google now calls this "Google Auth Platform", Branding and Audience pages).

  • App name: Outreach. Support email: your address.
  • User type / audience:
  • If the dedicated Gmail account is part of a Google Workspace organisation and the Cloud project belongs to that organisation, choose Internal. Internal apps need no verification and no test-user list. This is the simplest long-term setup.
  • If the account is a personal @gmail.com account, choose External.
  • Scopes: add https://www.googleapis.com/auth/gmail.send, openid and .../auth/userinfo.email.

External projects: Testing versus In production

An External project starts in Testing status. Testing works for up to 100 listed test users, but Google expires each test user's consent after seven days, so the app would stop sending every week until you reconnect. For an unattended system you want In production:

  1. On the Audience page, add your dedicated Gmail address as a test user (for the first connection).
  2. Click Publish app to move to In production.
  3. gmail.send is a restricted scope. An unverified app in production shows an "unverified app" warning during consent, which you click through once ("Advanced", then continue), and Google caps unverified apps at 100 users in total. With one dedicated account you never approach that cap. Google's verification documentation lists personal use ("you are the only user of your app") among the cases where verification is not required. If Google ever asks for verification, the Internal option above (a Workspace account) avoids the question entirely.

3. Create the OAuth client

APIs and Services, Credentials, Create credentials, OAuth client ID:

  • Application type: Web application.
  • Authorised redirect URI: https://<your host>/settings/gmail/callback (for a local test: http://localhost:8000/settings/gmail/callback).
  • Download the JSON and save it as data/credentials.json on the server (the path in GMAIL_CLIENT_SECRETS).

4. Connect from the admin

Settings, Connect Gmail. Sign in as the dedicated Gmail account, accept the permission. You land back in Settings with the address filled in. The token is stored at GMAIL_TOKEN_FILE (default data/gmail-token.json, mode 600) and refreshes itself; the refresh token stays valid until you disconnect, change the account password with the "sign out of everything" option, or revoke the app at https://myaccount.google.com/permissions.

Set TRANSPORT=gmail in .env and restart. Until then dryrun builds every message and records it without sending.

5. What the app sends

POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send with a JSON body of one field, raw: the complete RFC 2822 message (From, To, Subject, Message-ID, text body, the CV as a multipart/mixed PDF part) encoded base64url. The response's id and threadId are stored with the history row. Open any sent email in Outreach history to see the exact request that was built for it.

Sending limits

  • Google Workspace accounts: 2,000 messages per day (500 during a trial), applied over a rolling 24 hours; the Gmail API allows up to 500 recipients per message.
  • Personal @gmail.com accounts: Google's help page states the limit as 500 emails per day; hitting it blocks sending for 1 to 24 hours.

At the intended 1 to 2 emails per day the system never approaches either. Keep the daily cap in Settings low regardless: a dedicated account that suddenly sends hundreds of similar messages is what spam filters look for.

Troubleshooting

Symptom Cause and fix
"OAuth client file not found" GMAIL_CLIENT_SECRETS points nowhere; copy the downloaded JSON there
redirect_uri_mismatch on Google's page the URI in the client does not match https://<host>/settings/gmail/callback exactly (scheme, host, path)
"Gmail token is invalid and cannot be refreshed" consent expired (Testing status) or revoked; Publish the app and reconnect
HTTP 403 from the API on send the Gmail API is not enabled in the project, or the scope was not granted; reconnect
HTTP 429 daily sending quota reached; the run records the failure and retries on the next schedule

References

  • Sending email with the Gmail API: https://developers.google.com/workspace/gmail/api/guides/sending
  • Python quickstart (OAuth flow): https://developers.google.com/workspace/gmail/api/quickstart/python
  • App audience, Testing vs In production, verification: https://support.google.com/cloud/answer/15549945
  • Restricted scope verification and exemptions: https://developers.google.com/identity/protocols/oauth2/production-readiness/restricted-scope-verification
  • Workspace sending limits: https://knowledge.workspace.google.com/admin/gmail/gmail-sending-limits-in-google-workspace
  • Personal Gmail sending limits: https://support.google.com/mail/answer/22839