How to preserve formatting of google spreadsheet in mail merge?

Viewed 358

I want to send an email with google spreadsheet content as a body with formatting. I took the reference from here but it's only working with cell 'A1' and I want it to work with the complete data range. How to preserve all the formatting while sending the email?

enter image description here

1 Answers

You need to use getRichTextValues to get all the rich text values of each cell in the range given and then iterate each of them. Then compile them and format into a table.

Also, since the script didn't include the background, I have added it as well. See working script, sample data and the output below.

Script Modification:

const sendRichEmail = () => {
  // update cellAddress if needed, or use getDataRange below instead.
  const cellAddress = 'A1:B2';
  const sheetName = 'Mail Merge';
  const recipient = 'test@email.com';

  const richTextValue = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(sheetName)
    .getRange(cellAddress)
    .getRichTextValues();

  // Adding background color
  const backgroundColors = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(sheetName)
    .getRange(cellAddress)
    .getBackgrounds();

  /* Run is a stylized text string used to represent cell text.
     This function transforms the run into HTML with CSS
   */
  const getRunAsHtml = (richTextRun) => {
    const richText = richTextRun.getText();

    // Returns the rendered style of text in a cell.
    const style = richTextRun.getTextStyle();

    // Returns the link URL, or null if there is no link
    // or if there are multiple different links.
    const url = richTextRun.getLinkUrl();

    const styles = {
      color: style.getForegroundColor(),
      'font-family': style.getFontFamily(),
      'font-size': `${style.getFontSize()}pt`,
      'font-weight': style.isBold() ? 'bold' : '',
      'font-style': style.isItalic() ? 'italic' : '',
      'text-decoration': style.isUnderline() ? 'underline' : '',
    };

    // Gets whether or not the cell has strike-through.
    if (style.isStrikethrough()) {
      styles['text-decoration'] = `${styles['text-decoration']} line-through`;
    }

    const css = Object.keys(styles)
      .filter((attr) => styles[attr])
      .map((attr) => [attr, styles[attr]].join(':'))
      .join(';');

    const styledText = `<span style='${css}'>${richText}</span>`;
    return url ? `<a href='${url}'>${styledText}</a>` : styledText;
  };

  // Format the data that will work on multiple cells. 
  // Edit table properties if needed
  var finalBody = `<html><body><table border='1px'>`;
  /* Returns the Rich Text string split into an array of runs,
  wherein each run is the longest possible
  substring having a consistent text style. */
  for (var i = 0; i < richTextValue.length; i++) {
    finalBody += '<tr>';
    for (var j = 0; j < richTextValue[i].length; j++) {
      finalBody += `<td bgcolor='${backgroundColors[i][j]}'>`;
      finalBody += richTextValue[i][j].getRuns().map((run) => getRunAsHtml(run)).join('');
      finalBody += '</td>';
    }
    finalBody += '</tr>';
  }
  finalBody += '</table></body></html>';

  MailApp.sendEmail({to: recipient, subject: 'Rich HTML Email', htmlBody: finalBody});
};

Sample Data:

sample

Output:

output

Note:

  • I have also formatted it to have the data sent as table instead. Feel free to modify the properties of the table if needed.
  • To reduce the spacing between cells, use this:

Cellspacing 0:

var finalBody = `<html><body><table border='1' cellspacing='0'>`;

Output:

output2

Reference:

EDIT:

  • It is a limitation of getRichTextValues for both date objects and numbers. Alternatively, you can use getDisplayValues to insert those values but won't have the proper text styles due to getTextStyles returning nothing.

issue

Modified Script:

const sendRichEmail = () => {
  const sheetName = 'Sheet1';
  const recipient = 'test@email.com';

  const richTextValue = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(sheetName)
    .getDataRange()
    .getRichTextValues();

  // get string equivalent of the data
  const values = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(sheetName)
    .getDataRange()
    .getDisplayValues();

  const backgroundColors = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(sheetName)
    .getDataRange()
    .getBackgrounds();

  // pass the current index of row and column
  const getRunAsHtml = (richTextRun, i, j) => {
    var richText = richTextRun.getText();
    // if richText is empty, assign value from getDisplayValues
    if (!richText)
      richText = values[i][j];
    const style = richTextRun.getTextStyle();
    const url = richTextRun.getLinkUrl();

    const styles = {
      color: style.getForegroundColor(),
      'font-family': style.getFontFamily(),
      'font-size': `${style.getFontSize()}pt`,
      'font-weight': style.isBold() ? 'bold' : '',
      'font-style': style.isItalic() ? 'italic' : '',
      'text-decoration': style.isUnderline() ? 'underline' : '',
    };

    if (style.isStrikethrough()) {
      styles['text-decoration'] = `${styles['text-decoration']} line-through`;
    }

    const css = Object.keys(styles)
      .filter((attr) => styles[attr])
      .map((attr) => [attr, styles[attr]].join(':'))
      .join(';');

    const styledText = `<span style='${css}'>${richText}</span>`;
    return url ? `<a href='${url}'>${styledText}</a>` : styledText;
  };

  var finalBody = `<html><body><table border='1px'>`;
  for (var i = 0; i < richTextValue.length; i++) {
    finalBody += '<tr>';
    for (var j = 0; j < richTextValue[i].length; j++) {
      finalBody += `<td bgcolor='${backgroundColors[i][j]}'>`;
      // pass i and j into getRunAsHtml
      finalBody += richTextValue[i][j].getRuns().map((run) => getRunAsHtml(run, i, j)).join('');
      finalBody = finalBody.replace(/\n/g, '<br>');
      finalBody += '</td>';
    }
    finalBody += '</tr>';
  }
  finalBody += '</table></body></html>';


  MailApp.sendEmail({ to: recipient, subject: 'Rich HTML Email', htmlBody: finalBody });
};

Output:

output

Related