0. 投稿の動機
Salesforceに用意されているフローを使用したSlack通知ではなく、ApexクラスからSlack APIを呼び出して通知を送信する方法について学んだので、その実装方法の共有をしたいと思い本投稿を作成しました。
1. 読者の対象
・Apexクラス、LWCの実装経験のある方
・Apexクラスを使用してSlack通知を実装したい
2. 設定手順
以下の手順に従って設定をします。
① Slack ワークスペースとチャネルの作成(Slack側の設定)
② Slackアプリケーションの作成(Slack側の設定)
③ リモートサイトの設定(Salesforce側の設定)
④ Salesforce上に「Webhook URL」「チャネルID」を持たせる(Salesforce側の設定)
⑤ Slack通知を実行するApexクラスの作成(Salesforce側の設定)
2-①. Slack ワークスペースとチャネルの作成(Slack側の設定)
Slackワークスペースの作成とApexクラスから通知を受け取るためのチャネルを作成します。
図2-1

作成したチャネルを右クリックし、「チャネルの詳細を表示する」をクリックします。
表示されたチャネルの詳細にある「チャネルID」をどこかにメモしておきます。
図2-2

また、Slackユーザの「メンバーID」もメモしておく。
図2-3

2-②. Slackアプリケーションの作成(Slack側の設定)
Slack API 画面でアプリケーションを作成します。
「Create an App」ボタンをクリックし、アプリケーションを作成します。
図2-4
次の画面で「From scratch」を選択します。
図2-5

「App Name」にこれから作成するアプリケーション名を入力します。
(※「App Name」は半角英数字のみで設定する必要があることに注意する)
「Pick a workspace to develop your app in」では 2-① で作成したワークスペースを選択します。
上記2つの入力が完了したら「Create App」ボタンをクリックしてアプリケーションを作成します。
図2-6

次にメニューにある「Incoming Webhooks」を選択し、「Activate Incoming Webhooks」を有効化します。
図2-7

有効化した後画面をスクロールし、「Add New Webhook to Workspace」をクリックします。
図2-8

2-① で作成したSlack通知を受け取るためのチャネルを選択し、「許可する」ボタンをクリックします。
図2-9

生成された Webhook URL をどこかにメモしておきます。
図2-10

2-③. リモートサイトの設定(Salesforce側の設定)
Salesforceにログインし、設定画面を開きます。クイック検索に「リモートサイトの設定」を入力し、「リモートサイト設定」画面を開きます。
図2-11

「リモートサイト設定」画面にある「新規リモートサイト」ボタンをクリックします。「リモートサイト名」と「リモートサイトのURL」に「https://hooks.slack.com」を入力し、「保存」ボタンをクリックして設定内容を保存します。
図2-12

2-④. Salesforce上に「Webhook URL」「チャネルID」「メンバーID」を持たせる(Salesforce側の設定)
2-①でメモした「チャネルID」「メンバーID」と2-②でメモした「Webhook URL」をSalesforce上に持たせる。
今回の例では「メンバーID」はユーザーオブジェクトに保持し、「チャネルID」と「Webhook URL」はカスタムメタデータで保持する実装をしています。
したの画像の様に、ユーザーオブジェクトに「メンバーID」を設定できる項目を用意し、2-①でメモした「メンバーID」を入力して保存します。
図2-13

カスタムメタデータを作成し、「チャネルID」と「Webhook URL」を設定するための項目を用意します。
図2-14

メモしておいた「チャネルID」と「Webhook URL」をカスタムメタデータに登録します。
図2-15

1. 読者の対象
・Apexクラスを使用してSlack通知を実装したい
・Apexクラスのコーディングがある程度できる人
2. 設定手順
以下の手順に従って設定をします。
① Slack ワークスペースとチャネルの作成(Slack側の設定)
② Slackアプリケーションの作成(Slack側の設定)
③ リモートサイトの設定(Salesforce側の設定)
④ Salesforce上に「Webhook URL」「チャネルID」を持たせる(Salesforce側の設定)
⑤ Slack通知を実行するApexクラスの作成(Salesforce側の設定)
2-①. Slack ワークスペースとチャネルの作成(Slack側の設定)
Slackワークスペースの作成とApexクラスから通知を受け取るためのチャネルを作成します。
図2-16

作成したチャネルを右クリックし、「チャネルの詳細を表示する」をクリックします。
表示されたチャネルの詳細にある「チャネルID」をどこかにメモしておきます。
図2-17

また、Slackユーザの「メンバーID」もメモしておく。
図2-18

2-②. Slackアプリケーションの作成(Slack側の設定)
Slack API 画面でアプリケーションを作成します。
「Create an App」ボタンをクリックし、アプリケーションを作成します。
図2-19
次の画面で「From scratch」を選択します。
図2-20

「App Name」にこれから作成するアプリケーション名を入力します。
(※「App Name」は半角英数字のみで設定する必要があることに注意する)
「Pick a workspace to develop your app in」では 2-① で作成したワークスペースを選択します。
上記2つの入力が完了したら「Create App」ボタンをクリックしてアプリケーションを作成します。
図2-21

次にメニューにある「Incoming Webhooks」を選択し、「Activate Incoming Webhooks」を有効化します。
図2-22

有効化した後に画面をスクロールし、「Add New Webhook to Workspace」をクリックします。
図2-23

2-① で作成したSlack通知を受け取るためのチャネルを選択し、「許可する」ボタンをクリックします。
図2-24

生成された Webhook URL をどこかにメモしておきます。
図2-25

2-③. リモートサイトの設定(Salesforce側の設定)
Salesforceにログインし、設定画面を開きます。クイック検索に「リモートサイトの設定」を入力し、「リモートサイト設定」画面を開きます。
図2-26

「リモートサイト設定」画面にある「新規リモートサイト」ボタンをクリックします。「リモートサイト名」と「リモートサイトのURL」に「https://hooks.slack.com」を入力し、「保存」ボタンをクリックして設定内容を保存します。
図2-27

2-⑤. Slack通知を実行するApexクラスの作成(Salesforce側の設定)
次の様なLWCコンポーネント(slackSendMsgCmp)とApexクラス(SlackSendMsgCmpController.cls)を作成します。
SlackSendMsgCmpController.cls
public without sharing class SlackSendMsgCmpController {
@AuraEnabled
public static Map<String, String> sendToSlack(
String slackMessage, // Slack通知メッセージ
Boolean addChannelMention, // チャネルへのメンション付与フラグ
Boolean addUserlMention, // メンバーへのメンション付与フラグ
String objectApiName, // レコード詳細ページオブジェクトAPI参照名
String recordId // レコード詳細レコードID
){
// 実行結果格納Map
Map<String, String> resultMap = new Map<String, String>();
try {
// soql文
String soql = 'SELECT Id, Name'
+ ' FROM ' + objectApiName
+ ' WHERE Id = :recordId';
// レコードの取得
sObject redord = Database.query(soql);
// レコードName
String redord_name = String.valueOf(redord.get('Name'));
// レコードID
String redord_id = String.valueOf(redord.get('Id'));
// Slack通知メッセージの生成(メンションやレコード詳細ページのリンクの追加)
String message = generateMessage(slackMessage
, addChannelMention
, addUserlMention
, redord_name
, redord_id
, objectApiName);
Slack__mdt slack = [SELECT ChannelId__c
, WebhookUrl__c
FROM Slack__mdt
WHERE DeveloperName = 'SlackTest'];
// Requestボディに乗せるパラメータの生成
Map<String, String> paramMap = new Map<String, String>();
paramMap.put('channel', slack.ChannelId__c);
paramMap.put('text', message);
// Slackメッセージ送信実行
sendMessage(paramMap, slack.WebhookUrl__c);
// 実行結果格納
resultMap.put('title', 'Success!');
resultMap.put('message', 'Slackメッセージ送信に成功しました。');
resultMap.put('variant', 'success');
return resultMap;
} catch (Exception e) {
// エラー内容格納
resultMap.put('title', 'Failed...');
resultMap.put('message', e.getMessage());
resultMap.put('variant', 'error');
return resultMap;
}
}
/**
* Slack通知メッセージの生成(メンションやレコード詳細ページのリンクの追加)
*/
private static String generateMessage(
String slackMessage,
Boolean addChannelMention,
Boolean addUserlMention,
String redordName,
String redordId,
String objectApiName
){
String retMsg = '';
// チャネルメンション追加
if(addChannelMention){
retMsg = retMsg + '<!channel>' + '\r\n';
}
// メンバーメンション追加
if(addUserlMention){
// SlackメンバーIDの取得
User targetUser = [SELECT Id
, SlackMemberId__c
FROM User
WHERE Id = :UserInfo.getUserId()];
retMsg = retMsg + '<@' + targetUser.SlackMemberId__c + '>' + '\r\n';
}
// メッセージの追加
if(!String.isBlank(slackMessage)){
retMsg = retMsg + slackMessage;
}
// レコード詳細リンクの付与
retMsg = retMsg
+ '\r\n'
+ '\r\n'
+ createRecordPageUrl(objectApiName, redordId, redordName);
return retMsg;
}
/**
* レコード詳細ページURL生成
*/
private static String createRecordPageUrl(String objectApiName
, String recordId
, String linkName){
String url = URL.getOrgDomainUrl()
+ '/lightning/r/'
+ objectApiName
+ '/'
+ recordId
+ '/view';
return '<' + url + '|' + linkName + '>';
}
/**
* Slack通知Request送信
*/
@future (callout=true)
private static void sendMessage(
Map<String, String> paramMap,
String webHookUrl
) {
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(webHookUrl);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(paramMap));
HttpResponse res = http.send(req);
}
}
slackSendMsgCmp.js-meta.xml
<apiVersion>61.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
slackSendMsgCmp.html
<template>
<lightning-card title="Slackメッセージ送信">
<div
class="slds-p-around_medium" style="background-color: white;">
<div>
<lightning-textarea
label="メッセージ内容"
name="slackMessage"
onchange={handleChange}
required="true"
></lightning-textarea>
</div>
<div class="slds-m-top_small" >
<p>
<lightning-input
type="checkbox"
label="チャネルメンション追加"
name="addChannelMention"
checked={addChannelMention}
onchange={handleChange}
></lightning-input>
</p>
<p>
<lightning-input
type="checkbox"
label="ユーザーメンション追加"
name="addUserlMention"
checked={addUserlMention}
onchange={handleChange}
></lightning-input>
</p>
</div>
<div class="slds-m-top_small" style="display: flex; justify-content: end;">
<lightning-button
variant="brand"
label="送信"
name="send"
onclick={handleClick}
disabled={isDisabled}
></lightning-button>
</div>
</div>
</lightning-card>
</template>
slackSendMsgCmp.js
import { LightningElement, api, track } from 'lwc';
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import sendToSlack from '@salesforce/apex/SlackSendMsgCmpController.sendToSlack'
/**
*/
export default class SlackSendMsgCmp extends LightningElement {
@track slackMessage;
@track addChannelMention;
@track addUserlMention;
/**
* onchangeイベント
* @param {*} event
*/
handleChange = (event) => {
const name = event.target.name;
const checked = event.target.checked;
if(name === 'slackMessage'){
this.slackMessage = value;
}
if(name === 'addChannelMention'){
this.addChannelMention = checked;
}
if(name === 'addUserlMention'){
this.addUserlMention = checked;
}
}
/**
* onclickイベント
* @param {*} event
*/
handleClick = async(event) => {
const name = event.target.name;
if(name === 'send'){
await this.send();
}
}
/**
* Slack通知実行
*/
send = async() => {
let title = '';
let message = '';
let variant = '';
await sendToSlack({
slackMessage: this.slackMessage,
addChannelMention: this.addChannelMention,
addUserlMention: this.addUserlMention,
objectApiName: this.objectApiName,
recordId: this.recordId,
})
.then(res => {
title = res['title'];
message = res['message'];
variant = res['variant'];
})
.catch(err => {
title = 'Failed...';
message = err.message;
variant = 'error';
})
.finally(() => {
this.setDefault();
const evt = new ShowToastEvent({
title: title,
message: message,
variant: variant,
});
this.dispatchEvent(evt);
})
}
/**
* 入力値初期化
*/
setDefault = () => {
this.slackMessage = '';
this.addChannelMention = false;
this.addUserlMention = false;
}
/**
* 送信ボタン活性・非活性制御
*/
get isDisabled(){
return !this.slackMessage;
}
}
3. 実際にメッセージを送信してみる
ステップ2で作成したコンポーネントをレコード詳細ページに配備します。 本記事では取引先責任者に配備しています。 コンポーネントではチャネルへのメンション、メンバーへのメンションの追加が可能で、メッセージの後にレコード詳細ページのURLが自動で付与される仕様になっています。
図3-1

図3-2のようにメッセージ内容を入力し、チャネル、メンバーへのメンションを追加します。入力が完了したら「送信」ボタンをクリックして実際にSlackチャネルへメッセージを送信します。
図3-2

メッセージの送信に成功しました。
図3-3

Slackのチャネルを確認するとメッセージが送信されていることが確認できました。
図3-4

図3-5を見るとメンションが正しく機能していることが確認できます。また、メッセージにある「山田 花子」のリンクをクリックするとSalesforceの取引先責任者レコードページが表示されました(図3-6)。これでリンクが正しく機能していることも確認できました。
図3-5

図3-6

4. 最後に
私個人の感想としてはSalesforceのApexクラスを使用してSlackのメッセージ送信の機能を実装をすること自体は簡単にできるなと思いました。
ただ、実際にSalesforceを使用する方にとってどんなUI・UXが良いのかについては別途学習が必要だと感じました。
今回はメンション、メッセージの送信、リンクの付与のみの実装でしたが、今後はファイルの添付などもできるか試してみたいと思っています。
最後まで見ていただいきありがとうございました。