Google Suite domain-level signatures with Google Scripts
Tags: javascript
Published: 2020-03-30 15:52:34

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](https://www.impression.co.uk) 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](https://tanaikech.github.io/2019/07/05/linking-cloud-platform-project-to-google-apps-script-project/) 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](https://github.com/gsuitedevs/apps-script-oauth2). 

###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](https://developers.google.com/apps-script/advanced/admin-sdk-directory). 

###4. Allow domain-wide delegation 
From within your Google Admin, enable [Domain-Wide Delegation](https://admin.google.com/ac/owl/domainwidedelegation) for your Client ID on the specific following scopes. Follow the steps to add a new configuration. 

Scopes: 
- https://apps-apis.google.com/a/feeds/emailsettings/2.0/
- https://www.googleapis.com/auth/gmail.settings.basic
- https://www.googleapis.com/auth/gmail.settings.sharing

###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](https://support.google.com/a/answer/6208725?hl=en).

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](https://dev-to-uploads.s3.amazonaws.com/i/81kkvl5btvxtc2xvfcvg.png)

####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 Code.gs 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.

#####Code.gs
```javascript
var accountsToIgnore = [
  'ignore-me@example.com',
  'noreply@example.com'
];

var auth = {
  "private_key": "-----BEGIN PRIVATE KEY-----\nABCDE\n-----END PRIVATE KEY-----\n",
  "client_email": "name@project-id-XXX.iam.gserviceaccount.com",
  "client_id": "INSERT_CLIENT_ID_HERE"
};

function go() {  
  var pageToken;
  var page;
  
  do {
    page = AdminDirectory.Users.list({
      domain: 'example.com',
      orderBy: 'familyName',
      maxResults: 250,
      pageToken: pageToken,
      projection: 'full',
      // query: "email=your.email@example.com"
    });
    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,
            firstName: user.name.givenName,
            lastName: user.name.familyName,
            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(/{email}/g, userData.email)
          .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('https://www.googleapis.com/gmail/v1/users/%s/settings/sendAs/%s',userData.email, userData.email);
          
          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)
  .setTokenUrl('https://accounts.google.com/o/oauth2/token')
  .setPrivateKey(auth.private_key)
  .setIssuer(auth.client_email)
  .setPropertyStore(PropertiesService.getScriptProperties())
  .setSubject(userId)
  .setParam('access_type', 'offline')
  .setScope('https://www.googleapis.com/auth/gmail.settings.basic https://www.googleapis.com/auth/gmail.settings.sharing');
}

```

#####signature.html
```
<br />
<b style='font-size:small'>{firstName} {lastName}</b><br />
{jobTitle}Impression<br />
<a href='http://www.impression.co.uk/?utm_source=personalemail&amp;utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'><img alt='Impression' height='66' src='https://www.google.com/a/impression.co.uk/images/logo.gif' width='160' /></a><br />
<br />
T: 01158 242 212 {directNumber}<br />
E: {email}<br />
W: <a href='https://www.impression.co.uk/?utm_source=personalemail&amp;utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>www.impression.co.uk</a><br />
<br />
Specialists in <a href='https://www.impression.co.uk/digital-marketing/seo/?utm_source=personalemail&amp;utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>SEO</a>, <a href='https://www.impression.co.uk/digital-marketing/ppc/?utm_source=personalemail&amp;utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>PPC</a>, <a href='https://www.impression.co.uk/digital-marketing/digital-pr/?utm_source=personalemail&amp;utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>Digital PR</a> &amp; <a href='https://www.impression.co.uk/digital-marketing/analytics/?utm_source=personalemail&amp;utm_medium=email&amp;utm_campaign=EmailSig&amp;utm_content={firstName}-{lastName}' rel='nofollow'>Analytics</a>{workingHours}
```

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](https://dev-to-uploads.s3.amazonaws.com/i/8t6r7ck1z0biohhm6lhp.png)

####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 Code.gs. 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](https://twitter.com/aarondicks) if you have any specific questions!