
Twilio SendGrid: メール一斉送信
Twilio SendGrid: Send Bulk Email
この工程は、SendGrid のリストやセグメントに含まれる宛先に、メールを一斉送信します。
Basic Configs
- 工程名
- メモ
Configs for this Auto Step
- conf_Auth
- C1: API キーをトークンとして設定した認証設定 *
- conf_SenderId
- C2: 送信者 ID *
- conf_SendAt
- C3: 送信日時(指定しない場合、即座に送信されます)
- conf_ListIds
- C4-A: メールを送信する宛先リストの ID(文字型データ項目の場合、1 行に 1 つ)
- conf_SegmentIds
- C4-B: メールを送信する宛先セグメントの ID(文字型データ項目の場合、1 行に 1 つ)
- conf_UnsubscribeGroupId
- C5: 配信停止グループ ID *
- conf_DesignId
- C6-A: デザイン ID
- conf_HasUniqueContent
- C6-B: デザインを使用せず、件名と本文を直接指定する
- conf_Subject
- C6-B1: 件名 *#{EL}
- conf_HtmlContent
- C6-B2: HTML メールの本文 *#{EL}
- conf_PlainContent
- C6-B3: プレーンテキストメールの本文(指定しない場合、HTML メールの本文から自動生成されます)#{EL}
- conf_Categories
- C7: メール送信ログ検索用カテゴリ(文字型データ項目の場合、1 行に 1 つ)
- conf_SingleSendId
- C8: メール送信 ID を保存するデータ項目
- conf_SingleSendUrl
- C9: メール送信ステータス確認ページの URL を保存するデータ項目
Notes
- sendgrid.com が対象の自動工程です
- sendgrid.kke.co.jp は対象外です
- API キーの作成方法については、SendGrid のドキュメントを参照してください
- Restricted Access を選択する場合、Marketing の権限をオンにする必要があります


- 送信者 ID、リスト ID、セグメント ID、デザイン ID は、URL に含まれています
- app.sendgrid.com/settings/sender_auth/senders/(送信者 ID)/edit
- mc.sendgrid.com/contacts/lists/(リスト ID)
- mc.sendgrid.com/contacts/segments/(セグメント ID)
- mc.sendgrid.com/design-library/your-designs/(デザイン ID)/preview
- [C3: 送信日時]にデータ項目を指定している場合に、入力値が過去の日時や空では実行時エラーとなります
- 「送信先」タブでリスト ID とセグメント ID を両方指定すると、そのリストとセグメントに含まれるすべての宛先にメールが送信されます
- [C6-A: デザイン ID]を指定した場合、そのデザインに設定されているカテゴリがメール送信ログに適用されます
- [C7: メール送信ログ検索用カテゴリ]が設定できるのは、[C6-B: デザインを使用せず、件名と本文を直接指定する]を on にした場合のみです
- [C7: メール送信ログ検索用カテゴリ]に入力される文字には半角英数(ASCII)のみの制限があり、それ以外は実行時エラーとなります
Capture




See Also
Script (click to open)
- 次のスクリプトが記述されている XML ファイルをダウンロードできます
- sendgrid-email-send-bulk.xml (C) Questetra, Inc. (MIT License)
- Professional のワークフロー基盤では、ファイル内容を改変しオリジナルのアドオン自動工程として活用できます
const MAX_CATEGORY_NUM = 10;
const MAX_CATEGORY_LENGTH = 128;
const MAX_LIST_ID_NUM = 50;
const MAX_SEGMENT_ID_NUM = 10;
function main(){
//// == Config Retrieving / 工程コンフィグの参照 ==
const auth = configs.getObject('conf_Auth');
const senderId = retrieveIdAsInt('conf_SenderId', 'Sender ID');
const sendAt = retrieveSendAt();
const sendTo = retrieveSendTo();
const unsubscribeGroupId = retrieveIdAsInt('conf_UnsubscribeGroupId', 'Unsubscribe Group ID');
const content = retrieveContent();
const singleSendIdDef = configs.getObject('conf_SingleSendId');
const singleSendUrlDef = configs.getObject('conf_SingleSendUrl');
//// == Calculating / 演算 ==
let categories;
if (content.designId === undefined) { // デザインを使用しない場合のみ、config からカテゴリを読み出す
categories = retrieveCategories();
} else { // デザインを使用する場合は、デザインからカテゴリを取得する
categories = getCategoriesOfDesign(auth, content.designId);
}
const singleSendId = createSingleSend(auth, categories, senderId, sendTo, unsubscribeGroupId, content);
scheduleSingleSend(auth, singleSendId, sendAt);
//// == Data Updating / ワークフローデータへの代入 ==
setData(singleSendIdDef, singleSendId);
setData(singleSendUrlDef, `https://mc.sendgrid.com/single-sends/${singleSendId}/stats`);
}
/**
* config に設定されたカテゴリ一覧を読み出す
* 以下の場合はエラー
* - 件数が多すぎる
* - カテゴリが ASCII 文字でないものを含む
* - 文字数が多すぎる
* - カテゴリ指定が重複
* @return {Array<String>} categories
*/
function retrieveCategories() {
const categories = retrieveValuesAsList('conf_Categories', 'Categories', 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 に設定された ID を数値として読み出す
* @param {String} confName 設定名
* @param {String} label エラーメッセージ用ラベル
* @return {Number} id
*/
function retrieveIdAsInt(confName, label) {
const idStr = retrieveId(confName, label);
return validateIdAndReturnAsInt(idStr, label);
}
/**
* config に設定された ID を読み出す
* ID が設定されていない場合はエラー
* @param {String} confName 設定名
* @param {String} label エラーメッセージ用ラベル
* @return {String} id
*/
function retrieveId(confName, label) {
const idDef = configs.getObject(confName);
if (idDef === null) { // 固定値で指定
const id = configs.get(confName);
if (id === null || id === '') {
throw `${label} is blank.`;
}
return id;
}
// 文字型データ項目で指定
if (idDef.matchDataType('STRING_TEXTFIELD')) {
const id = engine.findData(idDef);
if (id === null || id === '') {
throw `${label} is blank.`;
}
return id;
}
// 選択型データ項目で指定
const selects = engine.findData(idDef);
if (selects === null || selects.size() === 0) {
throw `${label} is not selected.`;
}
return selects.get(0).getValue();
}
/**
* ID 文字列のバリデーションを行い、数値として返す
* @param {String} idStr ID 文字列
* @param {String} label エラーメッセージ用ラベル
* @return {Number} id
*/
function validateIdAndReturnAsInt(idStr, label) {
const reg = new RegExp('^\\d+$');
if (!reg.test(idStr)) {
throw `${label} must be non-negative integer.`;
}
return parseInt(idStr, 10);
}
/**
* config に設定された宛先情報を読み出す
* @return {Object} sendTo
* @return {Array<String>} sendTo.listIds
* @return {Array<String>} sendTo.segmentIds
*/
function retrieveSendTo() {
const listIds = retrieveValuesAsList('conf_ListIds', 'List IDs', MAX_LIST_ID_NUM);
const segmentIds = retrieveValuesAsList('conf_SegmentIds', 'Segment IDs', MAX_SEGMENT_ID_NUM);
if (listIds.length === 0 && segmentIds.length === 0) {
throw 'No List IDs or Segment IDs.';
}
return {listIds, segmentIds};
}
/**
* config に設定された ID 一覧を読み出す
* @param {String} confName 設定名
* @param {String} label エラーメッセージ用ラベル
* @param {Number} maxNum 最大件数
* @return {Array<String>} ids
*/
function retrieveValuesAsList(confName, label, maxNum) {
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 (ids.length > maxNum) {
throw `The maximum number of ${label} is ${maxNum}.`;
}
return ids;
}
// 選択型データ項目の場合
const selects = engine.findData(dataDef);
if (selects === null || selects.size() === 0) {
return [];
}
const ids = [];
selects.forEach(item => {
ids.push(item.getValue()); // 選択肢 ID を格納
});
if (ids.length > maxNum) {
throw `The maximum number of ${label} is ${maxNum}.`;
}
return ids;
}
/**
* config に設定された送信日時を文字列として読み出す
* データ項目が選択されていない場合は文字列 now を返す
* @return {String} sendAt
*/
function retrieveSendAt() {
const dataDef = configs.getObject('conf_SendAt');
if (dataDef === null) {
return 'now';
}
const datetime = engine.findData(dataDef); // AddableTimestamp
if (datetime === null) {
throw 'Scheduled Datetime is selected but its data is null.';
}
if (datetime.getTime() <= Date.now()) {
throw 'Scheduled Datetime must be future.';
}
return dateFormatter.format('UTC', "yyyy-MM-dd'T'HH:mm:ss'Z'", datetime);
}
/**
* config に設定されたメールの中身の情報を読み出す
* @return {Object} content メールの中身
* @return {String} content.designId デザイン ID
* @return {String} content.subject メールの件名
* @return {String} content.htmlContent HTML メールの本文
* @return {String} content.plainContent プレーンテキストメールの本文
*/
function retrieveContent() {
const hasUniqueContent = configs.getObject('conf_HasUniqueContent');
if (!hasUniqueContent) { // デザイン ID を指定する場合
const designId = retrieveId('conf_DesignId', 'Design ID');
return {designId};
}
// 件名、本文を直接指定する場合
throwErrorIfDesignIdIsSet();
const subject = configs.get('conf_Subject');
if (subject === '') {
throw 'Subject is blank.';
}
const htmlContent = configs.get('conf_HtmlContent');
if (htmlContent === '') {
throw 'HTML Content is blank.'
}
const plainContent = configs.get('conf_PlainContent');
return {subject, htmlContent, plainContent};
}
/**
* config にデザイン ID が設定されている場合、エラーをスローする
*/
function throwErrorIfDesignIdIsSet() {
const confValue = configs.get('conf_DesignId');
if (confValue !== '') {
throw 'Design ID is set while "Configure the subject and content without using Design" is enabled.';
}
}
/**
* デザインに設定されたカテゴリ一覧を取得する
* @param {AuthSettingWrapper} auth 認証設定
* @param {String} designId デザイン ID
* @return {Array<String>} categories
*/
function getCategoriesOfDesign(auth, designId) {
const response = httpClient.begin()
.authSetting(auth)
.get(`https://api.sendgrid.com/v3/designs/${encodeURIComponent(designId)}`);
const status = response.getStatusCode();
const responseStr = response.getResponseAsString();
switch (status) {
case 200:
break;
case 403:
engine.log(responseStr);
throw `Failed to get design. Read Access to Design Library is required for your API Key. status: ${status}`;
default:
engine.log(responseStr);
throw `Failed to get design. status: ${status}`;
}
return JSON.parse(responseStr).categories;
}
/**
* Single Send のドラフトを作成する
* @param {AuthSettingWrapper} auth 認証設定
* @param {Array<String>} categories カテゴリ一覧
* @param {Number} senderId 送信者 ID
* @param {Object} sendTo 宛先情報
* @param {Array<String>} sendTo.listIds 宛先リストの ID 一覧
* @param {Array<String>} sendTo.segmentIds 宛先セグメントの ID 一覧
* @param {Number} unsubscribeGroupId 配信停止グループ ID
* @param {Object} content メールの中身
* @return {String} singleSendId
*/
function createSingleSend(auth, categories, senderId, sendTo, unsubscribeGroupId, content) {
const requestBody = buildRequestBody(categories, senderId, sendTo, unsubscribeGroupId, content);
const response = httpClient.begin()
.authSetting(auth)
.body(JSON.stringify(requestBody), 'application/json')
.post('https://api.sendgrid.com/v3/marketing/singlesends');
const status = response.getStatusCode();
const responseStr = response.getResponseAsString();
if (status >= 300) {
engine.log(responseStr);
throw `Failed to create single send. status: ${status}`;
}
return JSON.parse(responseStr).id;
}
/**
* リクエストボディを作成
* @param {Array<String>} categories カテゴリ一覧
* @param {Number} senderId 送信者 ID
* @param {Object} sendTo 宛先情報
* @param {Array<String>} sendTo.listIds 宛先リストの ID 一覧
* @param {Array<String>} sendTo.segmentIds 宛先セグメントの ID 一覧
* @param {Number} unsubscribeGroupId 配信停止グループ ID
* @param {Object} content メールの中身
* @param {String} content.designId デザイン ID
* @param {String} content.subject メールの件名
* @param {String} content.htmlContent HTML メールの本文
* @param {String} content.plainContent プレーンテキストメールの本文
* @return {Object} requestBody
*/
function buildRequestBody(categories, senderId, sendTo, unsubscribeGroupId, {designId, subject, htmlContent, plainContent}) {
const emailConfig = {
sender_id: senderId,
suppression_group_id: unsubscribeGroupId
};
if (designId !== undefined) {
Object.assign(emailConfig, {
design_id: designId
});
} else {
Object.assign(emailConfig, {
subject,
html_content: htmlContent
});
if (plainContent !== '') {
Object.assign(emailConfig, {
generate_plain_content: false,
plain_content: plainContent
});
}
}
const requestBody = {
name: `Questetra-m${processInstance.getProcessModelInfoId()}-p${processInstance.getProcessInstanceId()}`,
categories,
send_to: {
list_ids: sendTo.listIds,
segment_ids: sendTo.segmentIds
},
email_config: emailConfig
};
return requestBody;
}
/**
* Single Send の送信予約をする
* @param {AuthSettingWrapper} auth 認証設定
* @param {String} singleSendId 送信設定の ID
* @param {String} sendAt 送信日時
*/
function scheduleSingleSend(auth, singleSendId, sendAt) {
const requestBody = {
send_at: sendAt
};
const response = httpClient.begin()
.authSetting(auth)
.body(JSON.stringify(requestBody), 'application/json')
.put(`https://api.sendgrid.com/v3/marketing/singlesends/${singleSendId}/schedule`);
const status = response.getStatusCode();
const responseStr = response.getResponseAsString();
if (status >= 300) {
engine.log(responseStr);
throw `Failed to schedule single send. status: ${status}, singleSendId: ${singleSendId}`;
}
}
/**
* データ項目にデータを保存する
* @param {DataDefinitionView} dataDef データ項目の DataDefinitionView
* @param {Object} value 保存する値
*/
function setData(dataDef, value) {
if (dataDef !== null) {
engine.setData(dataDef, value);
}
}