
Twilio SendGrid: Send Email
This item sends an email to the specified email addresses in To/Cc/Bcc, using SendGrid.
Basic Configs
- Step Name
- Note
Configs for this Auto Step
- conf_Auth
- C1: Authorization Setting in which API Key is set as token *
- conf_From
- C2: From Email Address (must be verified as sender on SendGrid) *
- conf_FromName
- C3: From Display Name
- conf_ReplyTo
- C4: Reply-To Email Address
- conf_ReplyToName
- C5: Reply-To Display Name
- conf_SendAt
- C6: Scheduled Datetime (if blank, sent immediately)
- conf_TrackOpen
- C7: Track if the email is opened
- conf_To
- C8: To Email Addresses (write one per line) *
- conf_Cc
- C9: Cc Email Addresses (write one per line)
- conf_Bcc
- C10: Bcc Email Addresses (write one per line)
- conf_Subject
- C11: Subject *#{EL}
- conf_HtmlContent
- C12-A1: HTML Content#{EL}
- conf_InlineImages
- C12-A2: Inline Images to insert to HTML Content
- conf_PlainContent
- C12-B: Plain Text Content#{EL}
- conf_Attachments
- C13: Attachments
- conf_Categories
- C14: Categories for filtering logs (write one per line)
Notes
- This item is intended for sendgrid.com
- It is not intended for sendgrid.kke.co.jp
- To learn about how to create your API Key, see the SendGrid Documentation
- When selecting Restricted Access, it is necessary to turn on the permission of Mail Send


- To insert inline images to the HTML body, use img tags as shown below
- The cid is the index number of the image in the data item, starting from 0
<img src="cid:0">
<img src="cid:1">
Capture




See Also
Script (click to open)
- An XML file that contains the code below is available to download
- sendgrid-email-send.xml (C) Questetra, Inc. (MIT License)
- If you are using Professional, you can modify the contents of this file and use it as your own add-on auto step
const MAX_CATEGORY_NUM = 10;
const MAX_CATEGORY_LENGTH = 255;
const MAX_RECIPIENT_NUM = 1000;
const MAX_SCHEDULABLE_HOURS = 72;
function main() {
//// == Config Retrieving / 工程コンフィグの参照 ==
const auth = configs.getObject('conf_Auth');
const from = retrieveEmailAndName('conf_From');
const replyTo = retrieveEmailAndName('conf_ReplyTo');
const sendAt = retrieveSendAt();
const trackOpen = configs.getObject('conf_TrackOpen');
const sendTo = retrieveSendTo();
const content = retrieveContent();
const attachments = retrieveAttachments();
const categories = retrieveCategories();
//// == Calculating / 演算 ==
sendMail(auth, from, replyTo, sendAt, trackOpen, sendTo, content, attachments, categories);
}
/**
* config に設定されたメールアドレスと表示名の情報を読み出す
* @param {String} emailConfName
* @return {Object} obj
* @return {String} obj.email
* @return {String} obj.name
*/
function retrieveEmailAndName(emailConfName) {
const email = configs.get(emailConfName);
if (email === null || email === '') {
return null;
}
const obj = { email };
const name = configs.get(`${emailConfName}Name`);
if (name !== null && name !== '') {
Object.assign(obj, { name });
}
return obj;
}
/**
* config に設定されたカテゴリ一覧を読み出す
* 以下の場合はエラー
* - 件数が多すぎる
* - カテゴリが ASCII 文字でないものを含む
* - 文字数が多すぎる
* - カテゴリ指定が重複
* @return {Array<String>} categories
*/
function retrieveCategories() {
const categories = retrieveValuesAsList('conf_Categories');
if (categories.length > MAX_CATEGORY_NUM) {
throw `The maximum number of Categories is ${MAX_CATEGORY_NUM}.`;
}
if (!categories.every(isAscii)) {
throw 'Categories cannot include non-ascii characters.';
}
// API ドキュメントに文字数制限の記載はないが、実際には制限がある
if (categories.some(category => category.length > MAX_CATEGORY_LENGTH)) {
throw `Each category must be within ${MAX_CATEGORY_LENGTH} characters.`;
}
const set = new Set(categories); // 重複確認用
if (categories.length !== set.size) {
throw 'The same category is set multiple times.';
}
return categories;
}
/**
* 文字列が ASCII 文字だけで構成されているかどうか
* @param {String} text テストする文字列
* @return {boolean} 文字列が ASCII 文字だけで構成されているかどうか
*/
function isAscii(text) {
const reg = new RegExp('^[\x00-\x7F]+$');
return reg.test(text);
}
/**
* config に設定された宛先情報を読み出す
* @return {Object} sendTo
* @return {Array<Object>} sendTo.to
* @return {Array<Object>} sendTo.cc
* @return {Array<Object>} sendTo.bcc
*/
function retrieveSendTo() {
const to = retrieveValuesAsList('conf_To', true);
if (to.length === 0) { // SendGrid の仕様で、To は必須
throw 'At least one To Email Address is required.';
}
const cc = retrieveValuesAsList('conf_Cc', true);
const bcc = retrieveValuesAsList('conf_Bcc', true);
const recipientNum = to.length + cc.length + bcc.length;
if (recipientNum > MAX_RECIPIENT_NUM) {
throw `The maximum number of recipients (To + Cc + Bcc) is ${MAX_RECIPIENT_NUM}.`;
}
const recipients = new Set(to.map(obj => obj.email).concat(cc.map(obj => obj.email)).concat(bcc.map(obj => obj.email))); // 重複確認用
if (recipientNum !== recipients.size) {
throw 'Each email address must be unique between To, Cc, and Bcc.';
}
const sendTo = { to };
// Cc と Bcc は、空でない場合にのみ設定する
if (cc.length > 0) {
Object.assign(sendTo, { cc });
}
if (bcc.length > 0) {
Object.assign(sendTo, { bcc });
}
return sendTo;
}
/**
* config に設定された値の一覧を読み出す
* @param {String} confName 設定名
* @param {boolean} returnAsEmailObj true の場合、メールアドレスと表示名のオブジェクトの配列を返す
* @return {Array<String>|Array<Object>} ids
*/
function retrieveValuesAsList(confName, returnAsEmailObj = false) {
const dataDef = configs.getObject(confName);
if (dataDef === null) {
return [];
}
// 文字型データ項目の場合
if (dataDef.matchDataType('STRING')) {
const dataObj = engine.findData(dataDef);
if (dataObj === null) {
return [];
}
const ids = dataObj.split('\n')
.filter(id => id !== '');
if (returnAsEmailObj) {
return ids.map((email) => ({ email }));
}
return ids;
}
// 選択型データ項目の場合
const selects = engine.findData(dataDef);
if (selects === null || selects.size() === 0) {
return [];
}
const ids = [];
selects.forEach(item => {
if (returnAsEmailObj) {
const email = item.getValue();
const name = item.getDisplay();
ids.push({ email, name });
return;
}
ids.push(item.getValue());
});
return ids;
}
/**
* config に設定された送信日時を UNIX タイムスタンプとして読み出す
* データ項目が選択されていない場合は文字列 null を返す
* @return {Number} sendAt
*/
function retrieveSendAt() {
const dataDef = configs.getObject('conf_SendAt');
if (dataDef === null) {
return null;
}
const datetime = engine.findData(dataDef); // AddableTimestamp
if (datetime === null) {
throw 'Scheduled Datetime is selected but its data is null.';
}
const sendAt = Math.floor(datetime.getTime() / 1000);
const now = Math.floor(Date.now() / 1000);
if (sendAt <= now) {
throw 'Scheduled Datetime must be future.';
}
if (sendAt - now > MAX_SCHEDULABLE_HOURS * 60 * 60) {
throw `Scheduled Datetime must be within ${MAX_SCHEDULABLE_HOURS} hours.`;
}
return sendAt;
}
/**
* config に設定されたメールの中身の情報を読み出す
* @return {Object} content メールの中身
* @return {String} content.subject メールの件名
* @return {String} content.htmlContent HTML メールの本文
* @return {String} content.plainContent プレーンテキストメールの本文
*/
function retrieveContent() {
const subject = configs.get('conf_Subject');
if (subject === '') {
throw 'Subject is blank.';
}
const htmlContent = configs.get('conf_HtmlContent');
const plainContent = configs.get('conf_PlainContent');
if (htmlContent === '' && plainContent === '') {
throw 'HTML Content and Plain Content are blank. At least one of them is required.';
}
return { subject, htmlContent, plainContent };
}
/**
* config に設定された添付ファイルの情報を読み出す
* @return {Array<Object>} attachments
*/
function retrieveAttachments() {
const attachments = [];
// インライン画像
const inlineImagesDef = configs.getObject('conf_InlineImages');
if (inlineImagesDef !== null) {
const qfiles = engine.findData(inlineImagesDef);
if (qfiles !== null) {
const varName = inlineImagesDef.getVarName();
for (let i = 0; i < qfiles.size(); i++) {
const qfile = qfiles.get(i);
const contentType = qfile.getContentType();
if (!contentType.startsWith('image/')) {
throw `${qfile.getName()} is not an image.`;
}
const obj = {
content: base64.encodeToString(fileRepository.readFile(qfile)),
filename: qfile.getName(),
type: contentType,
disposition: 'inline',
content_id: `${i}` // 0, 1, 2, ...
};
attachments.push(obj);
}
}
}
// 添付ファイル
const attachmentsDef = configs.getObject('conf_Attachments');
if (attachmentsDef !== null) {
const qfiles = engine.findData(attachmentsDef);
if (qfiles !== null) {
qfiles.forEach(qfile => {
const obj = {
content: base64.encodeToString(fileRepository.readFile(qfile)),
filename: qfile.getName(),
type: qfile.getContentType(),
disposition: 'attachment'
};
attachments.push(obj);
});
}
}
return attachments;
}
/**
* メールを送信する
* @param {AuthSettingWrapper} auth 認証設定
* @param {Object} from 送信元
* @param {Object} replyTo 返信先
* @param {Number} sendAt 送信日時
* @param {Boolean} trackOpen 開封検知を行うかどうか
* @param {Object} sendTo 宛先情報
* @param {Object} content メールの中身
* @param {Array<Object>} attachments 添付ファイル
* @param {Array<String>} categories カテゴリ一覧
*/
function sendMail(auth, from, replyTo, sendAt, trackOpen, sendTo, content, attachments, categories) {
const requestBody = buildRequestBody(from, replyTo, sendAt, trackOpen, sendTo, content, attachments, categories);
const response = httpClient.begin()
.authSetting(auth)
.body(JSON.stringify(requestBody), 'application/json')
.post('https://api.sendgrid.com/v3/mail/send');
const status = response.getStatusCode();
if (status !== 202) {
engine.log(response.getResponseAsString());
throw `Failed to send mail. status: ${status}`;
}
// リクエスト成功時のレスポンスは空
}
/**
* リクエストボディを作成
* @param {Object} from 送信元
* @param {Object} replyTo 返信先
* @param {Number} sendAt 送信日時
* @param {Boolean} trackOpen 開封検知を行うかどうか
* @param {Object} sendTo 宛先情報
* @param {Object} content メールの中身
* @param {String} content.subject メールの件名
* @param {String} content.htmlContent HTML メールの本文
* @param {String} content.plainContent プレーンテキストメールの本文
* @param {Array<Object>} attachments 添付ファイル
* @param {Array<String>} categories カテゴリ一覧
* @return {Object} requestBody
*/
function buildRequestBody(from, replyTo, sendAt, trackOpen, sendTo, { subject, htmlContent, plainContent }, attachments, categories) {
const requestBody = {
from,
reply_to: replyTo,
send_at: sendAt,
tracking_settings: {
open_tracking: {
enable: trackOpen
}
},
personalizations: [sendTo],
subject,
content: buildContent(plainContent, htmlContent)
};
if (attachments.length > 0) {
Object.assign(requestBody, { attachments });
}
if (categories.length > 0) {
Object.assign(requestBody, { categories });
}
return requestBody;
}
/**
* リクエストボディの content を作成
* @param {String} plainContent プレーンテキストメールの本文
* @param {String} htmlContent HTML メールの本文
* @return {Array<Object>} content
*/
function buildContent(plainContent, htmlContent) {
const content = [];
// プレーンテキストの本文がある場合、HTML 本文よりも先に設定する必要がある
if (plainContent !== '') {
content.push({
type: 'text/plain',
value: plainContent
});
}
if (htmlContent !== '') {
content.push({
type: 'text/html',
value: htmlContent
});
}
return content;
}