
Microsoft Teams: Post to Channel
This item posts a message, or a reply to a message, in a channel on Microsoft Teams.
Basic Configs
- Step Name
- Note
Configs for this Auto Step
- conf_OAuth2
- C1: OAuth2 Setting *
- conf_TargetUrl
- C2: Channel URL to post to / Message URL to reply to *
- conf_Subject
- C3: Subject of Message (ignored when posting a reply)#{EL}
- conf_Markdown
- C4: Post Content (Markdown) *#{EL}
- conf_PostUrl
- C5: Data item to save the URL of the posted message / reply
Notes
- To get Channel URL / Message URL, click the “…” icon (More options menu) next to the channel name or message and select “Copy link”
Capture

See Also
Script (click to open)
- An XML file that contains the code below is available to download
- microsoft-teams-channel-post.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
// OAuth2 config sample at [OAuth 2.0 Setting]
// - Authorization Endpoint URL: https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize
// - Token Endpoint URL: https://login.microsoftonline.com/organizations/oauth2/v2.0/token
// - Scope: ChannnelMessage.Send offline_access
// - Consumer Key: (Get by Microsoft Azure Active Directory)
// - Consumer Secret: (Get by Microsoft Azure Active Directory)
const GRAPH_URI = 'https://graph.microsoft.com/v1.0/';
const main = () => {
//// == 工程コンフィグの参照 / Config Retrieving ==
const oauth2 = configs.getObject('conf_OAuth2');
const targetUrl = retrieveTargetUrl();
const subject = configs.get('conf_Subject');
const markdownText = configs.get('conf_Markdown') ?? '';
if (markdownText === '') {
throw new Error('Post Content is empty.');
}
//// == 演算 / Calculating ==
const {teamId, channelId, messageId} = parseTargetUrl(targetUrl);
const postUrl = post(oauth2, teamId, channelId, messageId, subject, markdownText);
//// == ワークフローデータへの代入 / Data Updating ==
saveData('conf_PostUrl', postUrl);
};
/**
* config から投稿先 URL を読み出す
* 空の場合はエラー
* @returns {String}
*/
const retrieveTargetUrl = () => {
let targetUrl = configs.get('conf_TargetUrl');
const def = configs.getObject('conf_TargetUrl');
if (def !== null) {
targetUrl = engine.findData(def);
}
if (targetUrl === null || targetUrl === '') {
throw new Error('Target URL is empty.');
}
return targetUrl;
};
/**
* 投稿先の URL をパースし、teamId, channelId, messageId を返す
* チャンネル URL: https://{domain}/{path}/channel/{channelId}/{channelName}?groupId={teamId}&tenantId={tenantId}
* チャンネルメッセージ URL: https://{domain}/{path}/message/{channelId}/{messageId}?...&groupId={teamId}&...
* @param {String} targetUrl
* @returns {Object} teamId, channelId, messageId
*/
const parseTargetUrl = (targetUrl) => {
const [pathPart, queryPart] = targetUrl.split('?');
const pathSegments = pathPart.split('/');
const queryParams = parseQueryParams(queryPart);
// メッセージ URL の判定
const messageIndex = pathSegments.indexOf('message');
if (messageIndex !== -1 && messageIndex + 2 < pathSegments.length) {
const channelId = decodeURIComponent(pathSegments[messageIndex + 1]);
const messageId = decodeURIComponent(pathSegments[messageIndex + 2]);
const teamId = queryParams.groupId;
if (teamId === undefined) {
throw new Error(`groupId not found in target URL: ${targetUrl}`);
}
engine.log(`teamId: ${teamId}, channelId: ${channelId}, messageId: ${messageId}`);
return { teamId, channelId, messageId };
}
// チャンネル URL の判定
const channelIndex = pathSegments.indexOf('channel');
if (channelIndex === -1 || channelIndex + 1 >= pathSegments.length) {
throw new Error(`Invalid Target URL: ${targetUrl}`);
}
const channelId = decodeURIComponent(pathSegments[channelIndex + 1]);
const teamId = queryParams.groupId;
if (teamId === undefined) {
throw new Error(`groupId not found in target URL: ${targetUrl}`);
}
engine.log(`teamId: ${teamId}, channelId: ${channelId}`);
return { teamId, channelId, messageId: null };
};
/**
* クエリ文字列をパースしてオブジェクトにする
* @param {String|undefined} queryPart
* @returns {Object}
*/
const parseQueryParams = (queryPart) => {
if (queryPart === undefined) {
return {};
}
const params = {};
queryPart.split('&').forEach(param => {
const [key, ...rest] = param.split('=');
params[key] = rest.join('='); // value に = が含まれる場合を考慮
});
return params;
};
/**
* メッセージを投稿する
* @param {AuthSettingWrapper} oauth2 OAuth2 設定情報
* @param {String} teamId
* @param {String} channelId
* @param {String|null} messageId
* @param {Strung} subject
* @param {String} markdownText
* @returns {String} 投稿したメッセージの URL
*/
const post = (oauth2, teamId, channelId, messageId, subject, markdownText) => {
let url;
if (messageId !== null) { // チャンネルメッセージへの返信
url = `${GRAPH_URI}teams/${encodeURIComponent(teamId)}/channels/${encodeURIComponent(channelId)}/messages/${encodeURIComponent(messageId)}/replies`;
} else { // チャンネルへの投稿
url = `${GRAPH_URI}teams/${encodeURIComponent(teamId)}/channels/${encodeURIComponent(channelId)}/messages`;
}
const reqBody = {
body: {
contentType: 'html',
content: markdown.toHtml(markdownText)
}
};
if (subject !== '' && messageId === null) {
reqBody.subject = subject;
}
const response = httpClient.begin()
.authSetting(oauth2)
.body(JSON.stringify(reqBody), 'application/json; charset=UTF-8')
.post(url);
const status = response.getStatusCode();
const responseStr = response.getResponseAsString();
if (status !== 201) {
engine.log(responseStr);
throw new Error(`Failed to post message. status: ${status}`);
}
return JSON.parse(responseStr).webUrl;
};
/**
* データ項目への保存
* @param {String} configName
* @param {*} data
*/
const saveData = (configName, data) => {
const def = configs.getObject(configName);
if (def === null) {
return;
}
engine.setData(def, data);
};