Google Apps Script MailApp use document for htmlBody - google-apps-script

I have a simple document/template I would like to use when sending email to user. It works fine for text version, but I am unable to figure out, how to send the document in HTML format. The document only includes text with some bold formatting.
function sendEmail(emailAddress, attachment){
var EMAIL_TEMPLATE_ID = "SOME_GOOGLE_DOC_ID";
var emailTemplate = DocumentApp.openById(EMAIL_TEMPLATE_ID);
MailApp.sendEmail(emailAddress, "Subject", emailTemplate.getText(), {
htmlBody: emailTemplate, <-- THIS does not return correct data });
}

The answer I found is this one
function getDocAsHtml(docId){
var url = 'https://docs.google.com/feeds/download/documents/Export?exportFormat=html&format=html&id=';
return UrlFetchApp.fetch(url+docId).getContentText();
}
works pretty well

The htmlBody parameter expects a string while emailTemplate variable is of type Document.
You should use something like
MailApp.sendEmail(emailAddress, "Subject", emailTemplate.getText(), {
htmlBody: emailTemplate.getBody().getText() });

This is fairly straight-forward if we take advantage of Roman Vialard's DriveApp library. You will need to follow the instructions here to add the library to your project, and then you can use the code below.
/**
* get a String containing the contents of the given document as HTML.
* Uses DriveApp library, key Mi-n8njGmTTPutNPEIaArGOVJ5jnXUK_T.
*
* #param {String} docID ID of a Google Document
*
* #returns {String} Content of document, rendered in HTML.
*
* #see https://sites.google.com/site/scriptsexamples/new-connectors-to-google-services/driveservice
*/
function getDocAsHTML(docID) {
var doc = DriveApp.getFileById(docID);
var html = doc.file.content.src;
var response = UrlFetchApp.fetch(html);
var template = response.getContentText();
return template;
}
function sendEmail(emailAddress, attachment){
var EMAIL_TEMPLATE_ID = 'SOME_GOOGLE_DOC_ID';
var emailTemplate = getDocAsHTML(EMAIL_TEMPLATE_ID);
MailApp.sendEmail(emailAddress, "Subject", emailTemplate.getText(), {
htmlBody: emailTemplate });
}

Related

Sheets to Docs to Email

I have created a spreadsheet that contains quite a large amount of data.
The plan is to consolidate this data into a readable email to be sent out weekly, each specific row of data is its own email.
I tried going directly from sheets to email, but frankly it never quite looked right, plus the idea was to have a document template, where we could easily update the body without messing with code.
So I decided to write a email template in DOCS, set out a table, then have a script that copied the email template and updated the table with the row of data the script was looking at, then send it via email.
The code works great, but there is one little snag, the table never quite copies over to the email properly.
Below is are images of how the table is formatted in the email compared to the format in the template.
I just can not figure out how or why the format does not carry over.
I have also listed my code below, any help or advice on how I achieve the correct formatting would be appreciated.
UPDATE;
I have updated the question to show the code where we find the url of the document and convert to HTML,
var classArray=[];
//get html from Doc
var subject= row[30];
var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
var url = "https://docs.google.com/feeds/download/documents/export/Export?id="+newID+"&exportFormat=html";
var param = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions:true,
};
var html = UrlFetchApp.fetch(url,param).getContentText();
//docs uses css in the head, but gmail only takes it inline. need to move css inline.
//DOES NOT HANDLE HEADER CLASSES (eg h1, h2).
var headEnd = html.indexOf("</head>");
//get everything between <head> and </head>, remove quotes
var head = html.substring(html.indexOf("<head>")+6,headEnd).replace(/"/g,"");
//split on .c# with any positive integer amount of #s
var regex = /\.c\d{1,}/;
var classes = head.split(regex);
//get class info and put in an array index by class num. EG c4{size:small} will put "size:small" in classArray[4]
var totalLength = 0;
for(var i = 1; i < classes.length; i++){
//assume the first string (classes[0]) isn't a class definition
totalLength = totalLength + classes[i-1].length;
var cNum = head.substring(totalLength+2,head.indexOf("{",totalLength)); //totallength+2 chops off .c, so get what's between .c and {
totalLength = totalLength + 2 + cNum.length //add .c and the number of digits in the num
classArray[cNum] = classes[i].substring(1,classes[i].indexOf("}")); //put what's between .c#{ and } in classArray[#]
}
//now we have the class definitions, let's put it in the html
html = html.substring(headEnd+7,html.indexOf("</html>")); //get everything between <html> and </html>
var classMatch = /class=\"(c\d{1,} ){0,}(c\d{1,})\"/g
//matches class="c# c#..." where c#[space] occurs any number of times, even zero times, and c#[no space] occurs after it, exactly once
html = html.replace(classMatch,replacer); //replace class="c# c#..." with the definitions in classArray[#]
//make the e-mail!
GmailApp.sendEmail(row[31], subject, "HTML is not enabled in your email client. Sad face!", {
htmlBody: html,
});
function replacer(match){
var csOnly = match.substring(7,match.length-1); //class=" has 7 chars, remove the last "
var cs = csOnly.split(" "); //get each c#
var ret = "style=\""
for(var cCount = 0; cCount < cs.length; cCount++){
ret = ret + classArray[cs[cCount].substring(1)];
}
return ret+"\"";
}
})
}
The comments in the code says that Gmail can only use inline styling. That was true several years ago but currently Gmail allows to have a style tag inside a head tag. Considering this, the script could be much more simple that the one included in the question.
Below there is a script showing a sample that sends a Google Document content as the HTML body of an email message.
/**
* Get document as HTML
* Adapted from https://stackoverflow.com/a/28503601/1595451
*/
function getGoogleDocumentAsHTML(id) {
var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
var url = "https://docs.google.com/feeds/download/documents/export/Export?id=" + id + "&exportFormat=html";
var param = {
method: "get",
headers: { "Authorization": "Bearer " + ScriptApp.getOAuthToken() },
muteHttpExceptions: true,
};
var html = UrlFetchApp.fetch(url, param).getContentText();
return html;
}
/**
* Send the content of a Google Document as the HTML body of a email message
*/
function sendEmail(){
const url = /* add here the URL of your Google Document */;
const id = url.match(/[^\/]{44}/)[0];
const doc = getGoogleDocumentAsHTML(id);
const head = doc
.replace(/<meta[^>]+?>/g,'') // get rid of the meta tags
.match(/<head.+?<\/head>/)[0];
const body = doc.match(/<body[^>]+?>.+<\/body>/)[0];
const htmlBody = [head,body].join('\n');
MailApp.sendEmail({
to: /*add here the recipient email address */,
subject: /*add here the email subject */,
htmlBody: htmlBody
})
}
NOTE: You might want to clear the class of the body tag to avoid the margins set for it.

How to send rich text emails with GmailApp?

I’m trying to send a Google Doc, with all of its formatting, in an email.
function sendGoogleDocInEmail() {
var doc = DocumentApp.openById("example_Id");
var body = doc.getBody();
var text = body.getText();
GmailApp.sendEmail("emailaddress#gmail.com", "Hi", text);
This code works fine, but the email is sent as plain text.
I have descriptive hyperlink text pieces in the Google Doc, and they lose their hyperlinks when converted to plain text.
Is there any way I can keep all of the hypertext formatting when sending the email?
I’ve tried passing the body object to the method instead, but that just sends an email with DocumentBodySection in the body.
Thanks!
Trying using a combination of this script: https://stackoverflow.com/a/28503601/3520117
And then using the htmlBody parameter of the MailApp.sendEmail method.
Untested, but should work:
function emailGoogleDoc(){
var id = DocumentApp.getActiveDocument().getId() ;
var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
var url = "https://docs.google.com/feeds/download/documents/export/Export?id="+id+"&exportFormat=html";
var param = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions:true,
};
var html = UrlFetchApp.fetch(url,param).getContentText();
Logger.log(html);
var email = person#domain.tld;
var subject = 'Subject line';
var body = "To view this email, please enable html in your email client.";
MailApp.sendEmail(
email, // recipient
subject, // subject
body, { // body
htmlBody: html // advanced options
}
);
}

How to Embed a Google Form in Email in Google App Script

I have a form that requires login and I am trying to fetch its html to embed in an email. Similar to the question Google Apps Script: how to access or call "Send this form to others"?
var form = FormApp.create('New Form');
form.setRequireLogin(true);
...
var url = form.getPublishedUrl();
var response = UrlFetchApp.fetch(url);
var htmlBody = HtmlService.createHtmlOutput(response).getContent();
MailApp.sendEmail({
to: email,
subject: subject,
htmlBody: htmlBody,
});
...
Howerver, the URLFetchApp does not seem to have the correct OAuth configuration and I always get the HTML for Google Login page.
Is there a way to set the correct OAuth parameters to get the form HTML?
Your question asks about the OAuth parameters required to allow UrlFetch() to obtain the HTML content of a form with requiresLogin() set. This answer doesn't address that directly.
Without requiring OAuth, you can briefly change the login requirement for the form, just long enough to grab the HTML from it. For a small amount of time, your form could be accessed by individuals outside of your domain, if they happened to have the URL, and filled the form fast enough to submit their response before you locked the form up again.
Script
The following sendForm() function will work for consumer & GApps domain accounts, whether or not requiresLogin() is set.
/**
* Send user an email containing the given form, in HTML.
*
* #param {Form} form Form object.
* #param {String} email One or more email addresses, comma separated.
*/
function sendForm(form,email) {
var url = form.getPublishedUrl();
// Temporarily disable requiresLogin so UrlFetch will operate
if (form.requiresLogin()) {
var requiresLogin = true;
form.setRequireLogin(false);
}
// Fetch form's HTML
var response = UrlFetchApp.fetch(url);
var htmlBody = HtmlService.createHtmlOutput(response).getContent();
// Re-enable requireLogin, if necessary
if (requiresLogin) {
form.setRequireLogin(true);
}
var subject = form.getTitle();
MailApp.sendEmail(email,
subject,
'This message requires HTML support to view.',
{
name: 'Form Emailer Script',
htmlBody: htmlBody
});
}
For completeness, here's a test function...
function test_sendForm() {
// Build new form for testing
var form = FormApp.create('New Form');
var formTitle = 'Form Name';
form.setTitle(formTitle)
.setDescription('Description of form')
.setConfirmationMessage('Thanks for responding!')
.setAllowResponseEdits(true)
.setAcceptingResponses(true)
// Require Login (for GApp Domain accounts only)
try {
form.setRequireLogin(true);
} catch (e) {
// Error is expected for consumer accounts - carry on.
}
// Just one question
form.addTextItem().setTitle("Q1");
// Send it to self
var email = Session.getEffectiveUser().getEmail();
sendForm(form,email)
}

(Gmail) Sending emails from spreadsheet. How to add signature with image?

Due to the large amount of emails i'm sending with GMAIL i decided to automatize this process using a script and following this tutorial.Tutorial: Sending emails from a Spreadsheet
The "Message" is being generated by another function i created, called prepareEmails.
The problems are the following:
1) How can i tell prepareEmails to add my personal signature? I can't simply copy its text into that function, because my signature contains an image (for which i have the URL), and i want that image to be into the signature.
2) How can i make my signature BOLD?
Thanks everybody
There is an open Issue 2441 requesting the ability to append gmail signatures to email when using the GMailService. Visit and star it to receive updates.
As #wchiquito suggests, you can craft a script to attach images, producing a signature. You can also use HTML tags such as <B></B> to render text in bold, and so on.
Here's a different approach that will instead use a draft email as a template. This way, you can produce your signature with a variety of fonts and images using the online editor, and end up with a capability similar to automatic signature insertion.
The template needs to be saved in your Drafts folder, and it needs to have a tag indicating where the body of emails should go.
Example
function sendWithTemplate() {
var msgBody = "Test of sending a message using a template with a signature.";
sendGmailTemplate(Session.getActiveUser().getEmail(), 'test', msgBody );
}
Script
/**
* Insert the given email body text into an email template, and send
* it to the indicated recipient. The template is a draft message with
* the subject "TEMPLATE"; if the template message is not found, an
* exception will be thrown. The template must contain text indicating
* where email content should be placed: {BODY}.
*
* #param {String} recipient Email address to send message to.
* #param {String} subject Subject line for email.
* #param {String} body Email content, may be plain text or HTML.
* #param {Object} options (optional) Options as supported by GmailApp.
*
* #returns GmailApp the Gmail service, useful for chaining
*/
function sendGmailTemplate(recipient, subject, body, options) {
options = options || {}; // default is no options
var drafts = GmailApp.getDraftMessages();
var found = false;
for (var i=0; i<drafts.length && !found; i++) {
if (drafts[i].getSubject() == "TEMPLATE") {
found = true;
var template = drafts[i];
}
}
if (!found) throw new Error( "TEMPLATE not found in drafts folder" );
// Generate htmlBody from template, with provided text body
var imgUpdates = updateInlineImages(template);
options.htmlBody = imgUpdates.templateBody.replace('{BODY}', body);
options.attachments = imgUpdates.attachments;
options.inlineImages = imgUpdates.inlineImages;
return GmailApp.sendEmail(recipient, subject, body, options);
}
/**
* This function was adapted from YetAnotherMailMerge by Romain Vaillard.
* Given a template email message, identify any attachments that are used
* as inline images in the message, and move them from the attachments list
* to the inlineImages list, updating the body of the message accordingly.
*
* #param {GmailMessage} template Message to use as template
* #returns {Object} An object containing the updated
* templateBody, attachments and inlineImages.
*/
function updateInlineImages(template) {
//////////////////////////////////////////////////////////////////////////////
// Get inline images and make sure they stay as inline images
//////////////////////////////////////////////////////////////////////////////
var templateBody = template.getBody();
var rawContent = template.getRawContent();
var attachments = template.getAttachments();
var regMessageId = new RegExp(template.getId(), "g");
if (templateBody.match(regMessageId) != null) {
var inlineImages = {};
var nbrOfImg = templateBody.match(regMessageId).length;
var imgVars = templateBody.match(/<img[^>]+>/g);
var imgToReplace = [];
if(imgVars != null){
for (var i = 0; i < imgVars.length; i++) {
if (imgVars[i].search(regMessageId) != -1) {
var id = imgVars[i].match(/realattid=([^&]+)&/);
if (id != null) {
var temp = rawContent.split(id[1])[1];
temp = temp.substr(temp.lastIndexOf('Content-Type'));
var imgTitle = temp.match(/name="([^"]+)"/);
if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id[1]]);
}
}
}
}
for (var i = 0; i < imgToReplace.length; i++) {
for (var j = 0; j < attachments.length; j++) {
if(attachments[j].getName() == imgToReplace[i][0]) {
inlineImages[imgToReplace[i][2]] = attachments[j].copyBlob();
attachments.splice(j, 1);
var newImg = imgToReplace[i][1].replace(/src="[^\"]+\"/, "src=\"cid:" + imgToReplace[i][2] + "\"");
templateBody = templateBody.replace(imgToReplace[i][1], newImg);
}
}
}
}
var updatedTemplate = {
templateBody: templateBody,
attachments: attachments,
inlineImages: inlineImages
}
return updatedTemplate;
}
Credit where credit is due: The "Yet Another Mail Merge" script includes code that preserves inline images in emails during a mail merge - I've borrowed from that. Thanks Romain!
Currently the API does not offer anything to include the signature to messages, however, if you have control of the signature, you can use the method sendEmail(recipient, subject, body, options) available in classes GmailApp/MailApp. The options parameter allows you to set additional parameters to get what you need, for example, include images, with which the signature can build manually, and sets text in bold.
I invite you to take a look at the documentation/examples and post any questions you may have. An interesting example can be found here.

Get Google Document as HTML

I had a wild idea that I could build a website blog for an unsophisticated user friend using Google Drive Documents to back it. I was able to create a contentService that compiles a list of documents. However, I can't see a way to convert the document to HTML. I know that Google can render documents in a web page, so I wondered if it was possible to get a rendered version for use in my content service.
Is this possible?
You can try this code :
function getGoogleDocumentAsHTML(){
var id = DocumentApp.getActiveDocument().getId() ;
var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
var url = "https://docs.google.com/feeds/download/documents/export/Export?id="+id+"&exportFormat=html";
var param = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions:true,
};
var html = UrlFetchApp.fetch(url,param).getContentText();
Logger.log(html);
}
Node.js Solution
Using the Google APIs Node.js Client
Here's how you can get a google doc as html using google drive's node.js client library.
// import googleapis npm package
var google = require('googleapis');
// variables
var fileId = '<google drive doc file id>',
accessToken = '<oauth access token>';
// oauth setup
var OAuth2 = google.auth.OAuth2,
OAuth2Client = new OAuth2();
// set oauth credentials
OAuth2Client.setCredentials({access_token: accessToken});
// google drive setup
var drive = google.drive({version: 'v3', auth: OAuth2Client});
// download file as text/html
var buffers = [];
drive.files.export(
{
fileId: fileId,
mimeType: 'text/html'
}
)
.on('error', function(err) {
// handle error
})
.on('data', function(data) {
buffers.push(data); // data is a buffer
})
.on('end', function() {
var buffer = Buffer.concat(buffers),
googleDocAsHtml = buffer.toString();
console.log(googleDocAsHtml);
});
Take a look at the Google Drive V3 download docs for more languages and options.
Google docs currently has a function to do this.
Just download to zip(.html) and you can have a zip archive with html & image (if inserted)
I know this is not solution based on code, but its working :)
There is no direct method in GAS to get an HTML version of a doc and this is quite an old enhancement request but the workaround described originally by Henrique Abreu works pretty well, I use it all the time...
The only annoying thing in the authorization process that needs to be called from the script editor which makes it uneasy to use in a shared application (with "script unable" users) but this only happens once ;).
There is also a Library created by Romain Vialard that makes things (a bit) easier... and adds a few other interesting functions.
Here is a little snipped for the new version of goole AOuth following the idea posted by Enrique:
function exportAsHTML(){
var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
var docID = DocumentApp.getActiveDocument().getId();
var url = "https://docs.google.com/feeds/download/documents/export/Export?id="+docID+"&exportFormat=html";
var param = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions:true,
};
var html = UrlFetchApp.fetch(url,param).getContentText();
return html;
}
and then use the usual mailApp:
function mailer(){
var docbody = exportAsHTML();
MailApp.sendEmail({
to: "email#mail.com",
subject: "document emailer",
htmlBody: docbody });
}
Hope the new workaround helps
JD
You may use the solution here
/**
* Converts a file to HTML. The Advanced Drive service must be enabled to use
* this function.
*/
function convertToHtml(fileId) {
var file = Drive.Files.get(fileId);
var htmlExportLink = file.exportLinks['text/html'];
if (!htmlExportLink) {
throw 'File cannot be converted to HTML.';
}
var oAuthToken = ScriptApp.getOAuthToken();
var response = UrlFetchApp.fetch(htmlExportLink, {
headers:{
'Authorization': 'Bearer ' + oAuthToken
},
muteHttpExceptions: true
});
if (!response.getResponseCode() == 200) {
throw 'Error converting to HTML: ' + response.getContentText();
}
return response.getContentText();
}
Pass as fileId, the id of the google doc and to enable advanced drive services follow the instructions here.
I've had this problem as well. The HTML that the Document HTML Export spits out is really ugly, so this was my solution:
/**
* Takes in a Google Doc ID, gets that doc in HTML format, cleans up the markup, and returns the resulting HTML string.
*
* #param {string} the id of the google doc
* #param {boolean} [useCaching] enable or disable caching. default true.
* #return {string} the doc's body in html format
*/
function getContent(id, useCaching) {
if (!id) {
throw "Please call this API with a valid Google Doc ID";
}
if (useCaching == null) {
useCaching = true;
}
if (typeof useCaching != "boolean") {
throw "If you're going to specify useCaching, it must be boolean.";
}
var cache = CacheService.getScriptCache();
var cached = cache.get(id); // see if we have a cached version of our parsed html
if (cached && useCaching) {
var html = cached;
Logger.log("Pulling doc html from cache...");
} else {
Logger.log("Grabbing and parsing fresh html from the doc...");
try {
var doc = DriveApp.getFileById(id);
} catch (err) {
throw "Please call this API with a valid Google Doc ID. " + err.message;
}
var docName = doc.getName();
var forDriveScope = DriveApp.getStorageUsed(); // needed to get Drive Scope requested in ScriptApp.getOAuthToken();
var url = "https://docs.google.com/feeds/download/documents/export/Export?id=" + id + "&exportFormat=html";
var param = {
method: "get",
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions:true,
};
var html = UrlFetchApp.fetch(url, param).getContentText();
// nuke the whole head section, including the stylesheet and meta tag
html = html.replace(/<head>.*<\/head>/, '');
// remove almost all html attributes
html = html.replace(/ (id|class|style|start|colspan|rowspan)="[^"]*"/g, '');
// remove all of the spans, as well as the outer html and body
html = html.replace(/<(span|\/span|body|\/body|html|\/html)>/g, '');
// clearly the superior way of denoting line breaks
html = html.replace(/<br>/g, '<br />');
cache.put(id, html, 900) // cache doc contents for 15 minutes, in case we get a lot of requests
}
Logger.log(html);
return html;
}
https://gist.github.com/leoherzog/cc229d14a89e6327336177bb07ac2980
Perhaps this would work for you...
function doGet() {
var blob = DriveApp.getFileById('myFileId').getAsHTML();
return HtmlService.createHtmlOutput(blob);
}