DEV Community

Aaron Dicks for Impression

Posted on • Updated on

Google Suite domain-level signatures with Google Scripts

Getting Started

In this post I'll show you how to create a Google Apps Script with domain-level permissions to set your users signatures. This is great for maintaining consistency across a large or distributed team.

There are a number of Google Suite Domain signature software applications out there, but we have fairly straightforward requirements and wanted to create a cost-free solution. Previously we used Signature Satori but for our simple needs, this was overkill.

Our requirements at Impression were that all signatures were to:

  • be consistent
  • contain tracking URLs for Google Analytics
  • optionally contain a job title
  • optionally contain a direct dial
  • optionally contain an additional message, for part time staff

Additionally we have a number of service accounts which we do not want to include a signature on, as they have account names like "Impression Accounts".

Things to set up

The permissions required by this script span outside of the usual permissions a Google Script requires, so there's a few bits you'll need to set up in advance.

1. Connect to a Google Cloud Console project

There's a good set of instructions and background here but essentially it's important to realise that for advanced permission setting, your Google Script must be incorporated into a Google Cloud Project, with enabled billing. It's from within here you can enable API access and generate your required credentials.

Enable the following APIs;

  • Gmail API
  • Admin SDK

Configure a Service Account under "Credentials" and save the output file

2. OAuth 2.0

OAuth 2.0 can be added to Google Scripts via the Resources > Libraries dialog. Just so these instructions do not get outdated, please read the originals here on GitHub.

3. Enable "Admin SDK Directory Service"

This is an advanced admin-only G Suite permission that's required to list domain users. Read more on how to do this here with the latest Google instructions.

4. Allow domain-wide delegation

From within your Google Admin, enable Domain-Wide Delegation for your Client ID on the specific following scopes. Follow the steps to add a new configuration.


5. Optionally set up you additional Google User Admin fields

As mentioned above, our requirements here is that we show additional signature settings, such as job title, working hours, Google Voice numbers. Your usage may vary, but as we all know it's easy to strip back from a code sample than add to it. So this code includes all of the above.

To add additional fields (called "custom user attributes" in Google speak), read this help article.

We have the following fields set up under the new category "Email signature":

  • Show_job_title_in_signature (Yes/No)
  • Working_Hours_Description (Text)

Custom user attribute settings inside Google Admin

6. Load up this demo code

Don't forget to skip to the next section to learn which bits you should customise!

There are two files; the first should be pasted over your file. The second code snippet should be added to a new HTML file in the project. Head to File > New > HTML File to add this. Name it signature.html.
var accountsToIgnore = [

var auth = {
  "private_key": "-----BEGIN PRIVATE KEY-----\nABCDE\n-----END PRIVATE KEY-----\n",
  "client_email": "",
  "client_id": "INSERT_CLIENT_ID_HERE"

function go() {  
  var pageToken;
  var page;

  do {
    page = AdminDirectory.Users.list({
      domain: '',
      orderBy: 'familyName',
      maxResults: 250,
      pageToken: pageToken,
      projection: 'full',
      // query: ""
    if (page.users) {
      page.users.forEach( function (user){
        if (accountsToIgnore.indexOf(user.primaryEmail) == -1) {

        var service = getOAuthService(user.primaryEmail);
        // Pull in the signatire template file contents into this variable 
        var signatureTemplate = HtmlService.createHtmlOutputFromFile("signature").getContent();

          // Set up a userData variable, with some blank defaults as backups  
          var userData = {
            email: user.primaryEmail,
            jobTitle: "",
            showJobTitle: true,
            workingHours: "",
            directPhone: ""
          if (typeof user.customSchemas !== 'undefined') { // Email sig settings are set
            if (typeof user.customSchemas.Email_signature !== 'undefined') {

              if (typeof user.customSchemas.Email_signature.Show_job_title_in_signature !== 'undefined' && user.customSchemas.Email_signature.Show_job_title_in_signature == false) {
                userData.showJobTitle = false; 

              if (typeof user.customSchemas.Email_signature.Working_Hours_Description !== 'undefined' && user.customSchemas.Email_signature.Working_Hours_Description != "") {
                userData.workingHours = "<br /><br /><i>"+user.customSchemas.Email_signature.Working_Hours_Description+"</i><br />";


          if (user.hasOwnProperty('organizations') && user.organizations[0].hasOwnProperty('title') && typeof user.organizations[0].title !== "undefined" && userData.showJobTitle == true) {
            userData.jobTitle = user.organizations[0].title+"<br />";

          if (user.hasOwnProperty('phones') && Array.isArray(user.phones) && user.phones.length >0) {
            for (var p = 0; p < user.phones.length; p++) {
              if (user.phones[p].customType == "Google Voice") {
              // Depending on where in the world you are, you may need to adjust this formatting for your own needs... This replaces the +44 UK country code with a local "0" and adds a space after the local area code for formatting.
               userData.directPhone = "<br />D: " + user.phones[p].value.replace('+44', '0').replace('1158', '1158 '); 

          // Replace the placeholders as seen in the signature.html file with the actual data from the userData variable set up earlier. 
          var userSig = signatureTemplate
          .replace(/(\r\n|\n|\r)/gm, "")
          .replace(/{firstName}/g, userData.firstName)
          .replace(/{lastName}/g, userData.lastName)
          .replace(/{jobTitle}/g, userData.jobTitle)
          .replace(/{workingHours}/g, userData.workingHours)
          .replace(/{directNumber}/g, userData.directPhone); 

          var sigAPIUrl = Utilities.formatString('',,;

          var response = UrlFetchApp.fetch(sigAPIUrl, {
            method: "PUT",
            muteHttpExceptions: true,
            contentType: "application/json",
            headers: {
              Authorization: 'Bearer ' + service.getAccessToken()
            payload: JSON.stringify({
              'signature': userSig

          if (response.getResponseCode() !== 200) {
            Logger.log('There was an error: ' + response.getContentText());
          } else {
            Logger.log("Signature updated for "+user.primaryEmail);

    } else {
      Logger.log('No users found.');
    pageToken = page.nextPageToken;
  } while (pageToken);

function getOAuthService(userId) {
  return OAuth2.createService("Signature Setter "+userId)
  .setParam('access_type', 'offline')

Enter fullscreen mode Exit fullscreen mode
<br />
<b style='font-size:small'>{firstName} {lastName}</b><br />
{jobTitle}Impression<br />
<a href=';utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'><img alt='Impression' height='66' src='' width='160' /></a><br />
<br />
T: 01158 242 212 {directNumber}<br />
E: {email}<br />
W: <a href=';utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'></a><br />
<br />
Specialists in <a href=';utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>SEO</a>, <a href=';utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>PPC</a>, <a href=';utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>Digital PR</a> &amp; <a href=';utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>Analytics</a>{workingHours}
Enter fullscreen mode Exit fullscreen mode

By looking through the signature.html file you'll see plenty of placeholder values that we're using, like "{lastName}", embedded between our signature.

This aspect is one that you'll definitely want to customise to fit your own needs. Here's what our signature looks like:

Aaron Dicks email signature

7. Fill in your service credentials and authenticate the script

Insert your credentials from the Service Account you created inside your Google Cloud Project inside the "auth" variable in These should be self explanatory, and can be found if you open the JSON file in a text editor.

8. Testing

When testing, see the commented "query" line. Use this to query specific accounts, like your own, which comes in handy for live testing this solution.

Bear in in mind that your browser will not show a new email signature until you fully refresh all open Gmail windows.

9. Scheduling

Once created, it's worth thinking about using a daily trigger to ensure consistency is maintained across your business over time. We run the "go" script on a timer trigger, once nightly.

Thank you

I hope this is enough to get you started on your own custom signature adventures. Please hit me up on twitter @aarondicks if you have any specific questions!

Top comments (4)

macarthuror profile image

Do you have any other Docs or any tip of how to implement OAuth ?

I'm trying your code but I don't find out how to implement the auth part. I was looking for some example about it in the Gmail API and Workspace API but didn't have luck.

aarondicks profile image
Aaron Dicks

Hi, sorry, I've just seen this comment. I've since updated the domain-wide oauth delegation step above to include what I think you might have been missing. Apologies for the inconvenience. Thanks, Aaron

cdwarak profile image
Dwarakanath Cheyyur

Hi Aaron

I am getting an error - "Error: Access not granted or expired. Service_.getAccessToken @". any pointers? I have created the OAuth consent screen and the service account as detailed. The authorization takes place and then this error. I have set all the scopes as well. Could it be the private key has some issue?

joeriman profile image

Hi Aaron,

Thank you for your post!

Just to make sure: if I want to test the script for just 1 account, I only need to uncomment the query line and fill in the account details?
I don't want to mess up the signatures of the other accounts in the company...

Kind regards,