Back to blog

How to get and set a Gmail signature with the Gmail API

The Gmail API manages signatures through the sendAs resource, not a dedicated "signature" endpoint. To read a signature, call sendAs.get on gmail.googleapis.com/gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail} with GET. To write one, call sendAs.update (or sendAs.patch) on the same URL with a JSON body containing a signature field. For a single mailbox, standard OAuth is enough. For every user in a Google Workspace domain, you also need domain-wide delegation, because nobody is sitting at a consent screen for 200 mailboxes.

Quick answer:

  • Endpoint: gmail.googleapis.com/gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}
  • Read: GET, no request body, returns the SendAs resource (including signature)
  • Write: PATCH with {"signature": "<html>...</html>"} in the body
  • One mailbox: normal OAuth consent works fine
  • Every mailbox in a domain: needs domain-wide delegation and a service account, because you're acting *as* each user
  • Signature HTML has to be Gmail-safe - table-based markup with inline styles, no <style> blocks, no classes

What sendAs.get returns

sendAs.get reads one entry from a user's list of send-as identities (their primary address is one of these). A request looks like this:

`` GET https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs/jane%40example.com Authorization: Bearer {access_token} ``

The response is the SendAs resource for that address, which includes the signature field along with the rest of that identity's settings:

``json { "sendAsEmail": "jane@example.com", "displayName": "Jane Smith", "signature": "<table cellpadding=\"0\" cellspacing=\"0\">...</table>", "isPrimary": true, "isDefault": true, "treatAsAlias": false, "verificationStatus": "accepted" } ``

If you're only ever pushing a new signature and never need to preserve or diff against the old one, you can skip sendAs.get entirely and go straight to sendAs.update - that's what SignStampd does internally, since every push is a full template re-render rather than an edit of the existing signature.

What sendAs.update expects

sendAs.update (Gmail's API also exposes sendAs.patch for partial updates) takes the same URL and a body with the fields you want to change. Setting just the signature looks like this:

``` PATCH https://gmail.googleapis.com/gmail/v1/users/jane%40example.com/settings/sendAs/jane%40example.com Authorization: Bearer {access_token} Content-Type: application/json

{ "signature": "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"border-collapse: collapse;\">...</table>" } ```

Two details catch people out here:

  • `userId` and `sendAsEmail` are usually the same value when you're updating someone's primary signature, but they don't have to be - a user can have multiple send-as aliases, each with its own independent signature, so which alias you patch matters.
  • URL-encode the email address in both path segments. An @ that isn't encoded will 400.

A successful call returns 200 with the updated SendAs resource. A failed call returns Gmail's standard error shape (error.code, error.message, error.errors[].reason), which is worth parsing rather than just checking the status code, since "insufficient permission" and "invalid signature HTML" both come back as 4xx.

Why org-wide use needs domain-wide delegation

A single-user OAuth flow only ever lets you act as the person who clicked "Allow." Reading or updating hundreds of mailboxes without walking each employee through a consent screen requires the other Workspace auth path: a Google service account, authorized for domain-wide delegation by a Workspace admin (Admin console -> Security -> API Controls -> Domain-wide Delegation), then impersonating each user in turn.

Mechanically, that means signing a JWT per user rather than doing a normal OAuth redirect:

  • iss is the service account's client_email
  • sub is the email address you're impersonating for this call
  • scope is the space-separated list of scopes you're requesting
  • The JWT is signed with the service account's private key (RS256) and exchanged at the token endpoint for a short-lived access token scoped to that one user

The scopes you actually need for signature management are narrow - just two:

  • https://www.googleapis.com/auth/admin.directory.user.readonly - list the domain's directory (name, title, org unit) so you can personalize each signature
  • https://www.googleapis.com/auth/gmail.settings.basic - read and write the sendAs signature field

Neither scope grants access to email content, sending, or deleting messages - worth knowing if a security reviewer asks what a signature tool can actually do with the access it's requesting.

Gmail-safe HTML constraints

Whatever you put in the signature field has to survive Gmail's own HTML sanitization on every client - web, iOS, and Android - and that sanitization is stricter than a browser's:

  • Table-based layout, not `<div>`s. Gmail strips a lot of layout CSS on mobile clients in particular; nested <table> elements with cellpadding/cellspacing/border attributes survive far more reliably than flexbox or grid.
  • Inline styles only. <style> blocks and CSS classes get dropped. Every visual property - font, color, padding - needs to be a style attribute on the element itself.
  • Stay well under the practical size ceiling. Gmail signatures have a hard limit around 10,000 characters; bloated, deeply-nested markup (some third-party tools generate tables within tables within tables) burns through that budget fast and risks truncation.
  • Escape anything that came from user or directory data. A name or title pulled from the Directory API can contain characters like &, <, or " that need HTML-escaping before they go into the signature string, or you'll ship broken markup to that one person named "R&D Lead."

Rate limits when pushing to a whole domain

The Gmail API enforces per-user and per-project quotas, and sendAs.update counts against them like any other write. If you're looping over a directory of hundreds of users, two things matter: pace the calls (a small fixed delay - SignStampd uses 200ms between calls - keeps a bulk push well under quota without meaningfully slowing down a typical run), and isolate failures per user. One person's PATCH failing with a transient error shouldn't abort the loop for everyone after them; catch the error, record it, and move on to the next address.

The mobile-signature caveat

A signature written through sendAs.update appears in Gmail on web, and on iOS and Android - with one exception: if a user has already set their own signature inside the Gmail mobile app's own settings, that local override takes precedence on that device, and the API has no way to see or clear it. It isn't a bug in the endpoint; it's Gmail treating the mobile app's own signature setting as more specific than whatever was written server-side.

Building this yourself vs. using a tool

Everything above is enough to write a script that reads your Workspace directory and pushes a templated signature to everyone in it. What takes longer than the API calls themselves is the surrounding plumbing: JWT signing and token refresh per user, directory pagination, HTML escaping, per-user error isolation, and a way to re-run safely when someone joins later. If you'd rather not maintain that, a Gmail signature manager does exactly this - domain-wide delegation, directory-driven personalization, and sendAs.update pushes - through an admin dashboard instead of a script you have to babysit.

Managing signatures for a Google Workspace team? SignStampd handles the delegation, the directory sync, and the Gmail-safe HTML generation described above, then pushes to your whole team in one click. Start a 14-day free trial.

Managing signatures for a Google Workspace team? Start a free 14-day trial at SignStampd.

Start free trial