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.
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.
We have the following fields set up under the new category "Email signature":
- Show_job_title_in_signature (Yes/No)
- Working_Hours_Description (Text)
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
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&utm_medium=email&utm_campaign=EmailSig&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&utm_medium=email&utm_campaign=EmailSig&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&utm_medium=email&utm_campaign=EmailSig&utm_content={firstName}-{lastName}' rel='nofollow'>SEO</a>, <a href='https://www.impression.co.uk/digital-marketing/ppc/?utm_source=personalemail&utm_medium=email&utm_campaign=EmailSig&utm_content={firstName}-{lastName}' rel='nofollow'>PPC</a>, <a href='https://www.impression.co.uk/digital-marketing/digital-pr/?utm_source=personalemail&utm_medium=email&utm_campaign=EmailSig&utm_content={firstName}-{lastName}' rel='nofollow'>Digital PR</a> & <a href='https://www.impression.co.uk/digital-marketing/analytics/?utm_source=personalemail&utm_medium=email&utm_campaign=EmailSig&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:
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 if you have any specific questions!
Top comments (4)
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.
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
Hi Aaron
I am getting an error - "Error: Access not granted or expired. Service_.getAccessToken @ Service.gs:466". 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?
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,
Joeri