Gmail OAuth Sending: Fix 5 Errors That Stop Delivery

Gmail OAuth sending can fail before a message reaches the send queue. The fastest fix is to identify which layer rejected the request: consent, token exchange, scope, message encoding, or Gmail quota.
The Gmail API and SMTP with XOAUTH2 are both supported OAuth 2.0 routes. They don't accept the same scopes or message format, so a token that works in one path can fail in the other.
Quick answer
- Use
gmail.sendwhen your app only sends through the Gmail API.
- Use
https://mail.google.com/when your app authenticates to Gmail SMTP with XOAUTH2.
- Request offline access when a server needs a refresh token.
- Encode Gmail API messages as base64url. Encode the SMTP XOAUTH2 initial client response as one continuous base64 string.
- Treat
401,invalid_grant, and429as different failures. Each needs a different recovery path.
Gmail API vs SMTP XOAUTH2: choose the path first
Use the Gmail API when your application already sends over HTTP and wants the narrowest practical OAuth scope. A send-only integration can request https://www.googleapis.com/auth/gmail.send instead of full mailbox access.
Use SMTP XOAUTH2 when an existing mail library or application expects an SMTP transport. Google documents https://mail.google.com/ as the scope for IMAP, POP, and SMTP access, so a Gmail API token limited to gmail.send won't authenticate an SMTP session.
When setting up either approach, you'll need a few key things: a Google Cloud project, an OAuth consent configuration, client credentials, and a token lifecycle that fits your account type. Before you start building consent URLs or storing tokens, decide on a transport method. This choice will affect the scope, how data is formatted, and what errors might come up.
| Decision | Gmail API | SMTP with XOAUTH2 |
|---|---|---|
| Transport | HTTPS | SMTP |
| Send-only scope | gmail.send | Not supported |
| SMTP mailbox scope | Not required for send-only use | https://mail.google.com/ |
| Message format | RFC 2822 MIME in base64url | SMTP message after XOAUTH2 authentication |
| Good fit | New HTTP integrations | Existing SMTP clients |
What OAuth scopes does Gmail sending need?
Google recommends choosing the narrowest scope an application can use. For Gmail API sending, gmail.send allows the app to send on the user's behalf and is classified as a sensitive scope.
The broader https://mail.google.com/ scope can read, compose, send, and permanently delete Gmail mail. Google classifies it as restricted. Requesting it can add verification and data-handling obligations, so don't use it for a Gmail API sender that only needs gmail.send.
SMTP is the exception. Gmail's XOAUTH2 documentation specifies https://mail.google.com/ for SMTP access. If you mint a token with gmail.send and present it to smtp.gmail.com, the fix is to run consent again with the SMTP scope, not to keep retrying authentication.
For a server-side web application, add access_type=offline to the authorization request when the server needs to refresh access after the user leaves. Store the refresh token against the correct user and OAuth client. Never log it or place it in a job payload.
How do you build the SMTP XOAUTH2 value?
Gmail expects a SASL XOAUTH2 initial client response with this byte layout:
user={email}\x01auth=Bearer {access_token}\x01\x01
The separators are literal control-A bytes. They aren't the four visible characters \x01. Base64-encode the complete byte sequence, then send it as one continuous value after AUTH XOAUTH2.
Google's examples wrap long strings for readability, but the protocol value must contain no embedded whitespace. If a library inserts MIME-style line breaks, Gmail sees a malformed authentication value.
Connect to smtp.gmail.com with TLS. Google's current protocol page lists port 465 for SSL and port 587 for TLS with STARTTLS. Follow the security mode your SMTP library associates with the chosen port instead of treating the port number as the whole configuration.
How does Gmail API sending work?
Build a MIME message that complies with RFC 2822, encode the complete message as base64url, and put the result in the raw field of a Gmail Message resource. Send it with users.messages.send, or send an existing draft with users.drafts.send.
Base64url and standard base64 use different alphabets. A message can be valid MIME and still fail at the API boundary if the raw value isn't base64url encoded.
Keep token refresh separate from message submission. When an access token expires, exchange the refresh token for a new access token, then retry the send once. Repeating the same unauthorised send without refreshing only creates noise.
Five Gmail OAuth sending errors and their fixes
1. Scope mismatch
Symptom: Gmail API sending works, but SMTP returns an authentication failure. Or the token endpoint succeeds while the next Gmail operation reports insufficient permission.
Inspect the exact scopes granted to the token and compare them with the transport. Use gmail.send for send-only Gmail API access. Use https://mail.google.com/ for SMTP XOAUTH2. If the required scope wasn't granted, run a new consent flow and replace the stored grant for that account.
2. invalid_grant during token refresh
Symptom: The refresh request fails before Gmail receives a message.
Google lists several causes for an invalid refresh grant, including user revocation, password changes when Gmail scopes are present, time-limited access ending, an OAuth consent screen left in Testing, and token-count limits. External apps in Testing can receive refresh tokens that expire after seven days unless they request only basic identity scopes.
Don't loop on invalid_grant. Mark the connection as requiring reconnection, keep the failure visible to the user or operator, and start a new authorization flow. Check the OAuth app's publishing status before assuming token storage is broken.
3. Consent screen, client, or API configuration mismatch
Symptom: Google rejects the authorization request, the requested scope isn't granted, or the Gmail API call reports that access isn't configured.
Make sure the redirect URI is exactly the same as the one listed for the OAuth client. Also, check that the Gmail API is turned on for the same project. The consent screen should include the Gmail scope you're asking for. Sometimes, workspace administrators limit what third-party apps can do or which scopes they can access. This means the user might not be able to give permission until the administrator updates the policy.
Service accounts don't silently bypass user consent for ordinary Gmail accounts. Access to Workspace users requires domain-wide delegation authorised by a Workspace super administrator, plus explicit user impersonation in the delegated call.
4. Malformed SMTP or API payload
Symptom: Authentication succeeds, but SMTP rejects AUTH XOAUTH2), or the Gmail API rejects the raw` message.
When using SMTP, you need to check the decoded XOAUTH2 bytes carefully. Make sure you can see both control-A separators and that it starts with Bearer . Also, check that there are no line breaks in the encoded value. On the other hand, if you're using the Gmail API, the process is a bit different. First, verify the MIME message. Then, check that it's been encoded correctly using base64url and that it includes the raw property.
Test one known mailbox and one small plain-text message before adding HTML, attachments, pooling, or tenant routing. A minimal fixture separates authentication from message-building faults.
5. Quota or rate limiting mistaken for authentication failure
Symptom: Sends work and then begin returning 429, often after a burst or later in the day.
Gmail API quotas and Gmail's mail-sending limits are separate constraints, and the daily sending limit is shared across a user's clients. Google recommends truncated exponential backoff for time-based quota errors. A retry loop still needs a cap so one user can't keep a worker busy indefinitely.
A personal Gmail account can hit a daily limit after more than 500 emails or recipients. Workspace limits vary by account type and can change, so read the current Google limit page instead of hard-coding one universal number.
How should you store tokens for multiple users?
Treat every refresh token as a credential tied to one user, one OAuth client, and one granted scope set. Encrypt it at rest, restrict which service can decrypt it, and keep reconnection state separate from transient delivery retries.
Record enough metadata to diagnose failures without recording the token: account identifier, client identifier, granted scopes, connection time, last successful refresh, and the latest normalised error category. This makes a scope mismatch visible without exposing credentials.
Throttle by user as well as by project. Gmail applies per-user limits, so one busy tenant can otherwise consume worker capacity while every retry is destined to fail.
When a shared email API is the cleaner boundary
Using Direct Gmail OAuth makes sense when you're dealing with a small number of accounts. However, as the number of accounts grows, so does the operational cost. This is because the SaaS platform has to handle a lot of extra tasks, such as managing consent, refreshing tokens, keeping track of quotas, and reconnecting accounts. This can get complicated and expensive to manage across many customer mailboxes.
Sendmux is a multi-provider email API for AI agents and SaaS platforms. It can connect Gmail OAuth, Microsoft 365, SMTP, or Amazon SES for outbound sending, while the same product also provides inbound agent mailboxes. That gives an application one API boundary for sending and receiving without claiming that Gmail's own OAuth rules disappear.
Use direct Gmail OAuth when you want Gmail-specific API features and can own the token lifecycle. Use Sendmux sending when provider routing and one outbound API are the better boundary, and Sendmux inboxes when the application also needs dedicated inbound mailboxes.
Gmail OAuth sending checklist
- Choose Gmail API or SMTP before requesting scopes.
- Configure the OAuth client and exact redirect URI in the project that has the Gmail API enabled.
- To get API access, you can request
gmail.sendfor send-only access. Alternatively, if you need SMTP XOAUTH2, usehttps://mail.google.com/.
- Request offline access only when the server needs a refresh token.
- Validate one plain-text send with a known account.
- Classify 401 authentication, invalid_grant token, insufficient-scope, malformed-payload, and 429 quota failures separately.
- Use bounded backoff for retryable quota and server errors. Require reconnection for a revoked or invalid refresh grant.
So, when something goes wrong, there's a simple rule to follow: fix the part that's not working. If it's a consent issue, you need to check your settings. If it's a token problem, you have to reconnect. If it's a scope issue, you need to get a new grant. If the problem is with the payload, you have to look really closely at the individual bytes. And if it's a quota issue, you need to either use backoff or reduce the amount of traffic.
Primary references
Frequently Asked Questions
Does Gmail support OAuth?
Yes. Gmail supports OAuth 2.0 for the Gmail API and for SMTP authentication through SASL XOAUTH2. The scope and message format depend on which transport the application uses.
How do you enable OAuth for Gmail sending?
Create or select a Google Cloud project, enable the Gmail API, configure the OAuth consent screen, create an OAuth client, and request the scope required by the chosen transport. A server-side app that needs a refresh token must request offline access.
How do you remove OAuth permission in Gmail?
Remove the app's access from the Google Account's third-party connections page. The app should treat the resulting refresh failure as a reconnection requirement instead of repeatedly retrying the old grant.
Is Gmail SMTP port 465 or 587?
Gmail supports both. Port 465 uses implicit SSL, while port 587 starts with a plain connection and upgrades it with STARTTLS. Configure the matching security mode in the SMTP client.
Can Sendmux replace a direct Gmail OAuth integration?
Sendmux can provide one outbound API over connected Gmail OAuth, Microsoft 365, SMTP, or Amazon SES providers, and it also provides inbound agent mailboxes. Use Gmail directly when the application needs Gmail-specific API features.