domainwidedelegation.comGet it set up

Gmail API with a delegated service account

Gmail is the most common reason people set up domain-wide delegation, and the API with the most delegation-specific gotchas. A service account has no mailbox of its own — every Gmail call must be made as somebody.

The minimum working example

from google.oauth2 import service_account
from googleapiclient.discovery import build

creds = service_account.Credentials.from_service_account_file(
    "/secure/path/key.json",
    scopes=["https://www.googleapis.com/auth/gmail.readonly"],
).with_subject("person@yourdomain.com")

gmail = build("gmail", "v1", credentials=creds)
print(gmail.users().getProfile(userId="me").execute())

userId="me" means the impersonated subject, not the service account. This is the single most useful thing to internalise about the Gmail API under delegation.

Sending as an alias

A send-as alias belongs to a mailbox. To send from an alias, impersonate the mailbox that owns it and set the From header to the alias — impersonating the alias address directly fails, because it is not a user.

import base64
from email.message import EmailMessage

msg = EmailMessage()
msg["To"] = "someone@example.com"
msg["From"] = "alias@yourdomain.com"     # alias on the impersonated mailbox
msg["Subject"] = "Hello"
msg.set_content("Body text")

gmail.users().messages().send(
    userId="me",
    body={"raw": base64.urlsafe_b64encode(msg.as_bytes()).decode()},
).execute()

Three Gmail-specific traps

Drafts do not delete the way you expect

Deleting a draft through the drafts endpoint can return success while leaving the draft in place. Deleting the underlying message is what actually removes it. Whatever you call, verify with a follow-up fetch that expects a 404 — never trust the 200 alone.

Threading requires headers, not just a thread ID

Supplying threadId is not enough for a reply to nest properly in the recipient's client. Set In-Reply-To and References to the Message-ID of the message you are answering.

Search results lag reality

Gmail search is eventually consistent. Immediately after deleting or labelling, a query can still return the old state. When you need certainty, fetch by ID rather than trusting a search count.

Scopes for common Gmail jobs

JobScope
Read and report on mailgmail.readonly
Send notificationsgmail.send
Prepare drafts for a human to approvegmail.compose
Label, archive, organisegmail.modify
Manage signatures, filters, send-asgmail.settings.basic

Building something that touches every mailbox?

Automated triage, drafting, archiving and reporting across a domain is exactly what we build. We will set up the delegation and the Gmail pipeline, and verify it against real mailboxes before handover. $500 per hour.

Talk through your build