Translate

2011年5月4日水曜日

外部からの電子メール受信をトリガにApexクラスを起動するサンプル

Salesforce は CRM SaaS なので、

顧客からの問いあわせを割り振る機能は用意されている。

それが

電子メールtoケース or

オンデマンド電子メールtoケース

という機能だ。

ただこの機能は外部のメールサーバを利用する

というもの。



おそらく一般的にも

info@<会社のメールアドレス>

などへ問い合わせアドレスを顧客の目にさらす

場合があることを想定しているのだと思う。



ただ上記サービスは「ケース」オブジェクトへの連携

なので、

カスタムオブジェクトなどへの連携は宣言的開発では

出来なかった。



ただApexクラスに

Messaging.InboundEmailHandler インタフェイスを実装して

電子メールサービスに登録することで

自由度の聞くインバウンドメールをトリガとして起動する

プログラムを書くことが出来る。



以下、Force.com開発者コースの演習で使用した

電子メールサービスのサンプルコードである。





// Apex Code 演習9-1 インバウンドメールサービス(1)
// 到着した電子メールの本文を解釈して応募者(Candidate)レコードとして格納する
//
// 電子メールサービス「CandidateSubmission」のハンドラとして定義
// 「CandidateSubmission」宛メール到着→handlerInboundEmailメソッド呼び出し
// <登録すると割り当てられるメールアドレス名の例>
// candidatesubmission@
// x-xxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxx.9.apex.salesforce.com
// x:Salesforceが自動で割り当てる
//
// 受信するメール本文は以下の文字列形式となっていることが前提
//
// "[STARTBODY]firstname=" + FirstName文字列 +
// ":lastname=" + LastName文字列 +
// ":phone=" + Phone値 + ":comment=" + Comment文字列 + "[ENDBODY]"
//
//
// 電子メールハンドラクラスとなるための条件
// ・globalクラス
// ・Messaging.InboundEmailHandlerを実装
// - handleInboundEmail()を実装
global class CandidateEmailHandler implements Messaging.InboundEmailHandler {
// 電子メール到着時に呼び出されるメソッド
// 引数: email 受信した電子メールデータ
// envelope 受信した電子メールのエンベロープ
global Messaging.InboundEmailResult handleInboundEmail(
Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {

// 戻り値インスタンス生成
Messaging.InboundEmailResult result =
new Messaging.InboundEmailresult();

// デバッグメッセージ用文字列変数
String myErr;


try{
// メール本文を格納する文字列変数
String theBody;

// 属性別に切り出された文字列を格納するリスト
List<String> fieldList = new List<String>();

// 受信メールがテキスト形式
if (email.plainTextBody != null){
// デバッグメッセージ変数に書き込み
myErr = 'plainTextBody=' + email.plainTextBody;
// メール本文を格納
theBody = email.plainTextBody;

// 受信メールがテキスト形式ではない(HTML形式)
} else {
myErr = 'htmlBody=' + email.htmlBody;
theBody = email.htmlBody;
}

// 本文を属性別に分解する
// 開始文字列、終了文字列にはさまれた文字列を切り出す
theBody = theBody.substring(
theBody.indexOf('[STARTBODY]') + 11,
theBody.indexOf('[ENDBODY]'));
// セパレータ":"で分解しリスト化
fieldList = theBody.split(':',0);

// 分解元文字列と分解後をデバッグメッセージに追加
myErr += '\ntheBody=' + theBody;
myErr += '\nfieldList=' + fieldList;

// 属性名、属性値のMap化
Map<String,String> fieldMap = new Map<String,String>();

// 分解リストループ
for(String field : fieldList){
// "="で分割すると2つになる場合
if (field.split('=',0).size() == 2){
// 最初の要素をキー
// 2番目の要素を値として格納
fieldMap.put(
field.split('=',0)[0],
field.split('=',0)[1]);
}
}


// 応募者レコード
Candidate__c candidate;
try{
// 重複チェックのためにLastNameと電子メール
// アドレスが同じレコードを抽出
// 今回のアプリの仕様上LastNameとEmailで一意
// となっているはずなので存在しても1件のみ
// の想定であるためListではない
candidate =
[select id,
first_name__c,
last_name__c,
phone__c,
email__c,
ownerid
from candidate__c
where last_name__c =
:fieldMap.get(
'lastname')
and email__c =
:email.fromAddress];
// 既に存在する場合は、ほかの属性値更新のため
// このcandidate値を継続使用する
// 存在しない場合もQueryExceptionを発生させる
} catch (QueryException qe){
// candidateが存在しない場合は重複なしなので
// 新規作成する
if (candidate == null){
candidate = new Candidate__c();
}
// 処理継続させたいので再スローしない
}

// FirstName値上書き
candidate.first_name__c =
fieldMap.containsKey('firstname') ?
// 日本語が混じることも考えられるので
// EncodingUtilクラスを使ってエンコード
// する
EncodingUtil.urlDecode(
fieldMap.get('firstname'),'UTF-8') :
// nullの場合更新対象にならない
null;

// LastName値上書き
candidate.last_name__c =
fieldMap.containsKey('lastname') ?
EncodingUtil.urlDecode(
fieldMap.get('lastname'),'UTF-8') :
null;

// Phone値上書き
candidate.phone__c =
fieldMap.containsKey('phone') ?
EncodingUtil.urlDecode(
fieldMap.get('phone'),'UTF-8') :
null;
// Email値上書き
// 電子メールオブジェクトからなのでEncode不要
candidate.email__c = email.fromAddress;

try{
// 新規追加の場合
// (IDがまだ割り当てられていない)
if (candidate.id == null) {
// DBへ1件新規追加
insert candidate;
// 更新の場合
} else {
// 指定行を1件更新
update candidate;
}

// DB例外発生時処理
} catch (DMLException e){

// Error_Logレコードに例外情報を格納する
Error_Log__c log = new Error_Log__c();
log.trace__c = e.getTypeName() + '\n' +
e.getCause() + '\n' + e.getMessage() +
'\n\nfieldMap=' + fieldMap +
'\n\nmyErr=' + myErr;
insert log;

// 戻り値の成功フラグを偽にして返却
result.success = false;
return result;
}

// Comment欄データが存在する場合
// 応募者レコードのメモとして追加
// 「メモ&添付ファイル」関連レコードに格納される
if (fieldMap.containsKey('comment')){
// Note(メモ)レコード生成
Note cNote = new Note();
// Comment値をエンコードして格納
cNote.body = EncodingUtil.urlDecode(
fieldMap.get('comment'),'UTF-8');
cNote.parentId = candidate.id;
// タイトル文字列を格納
cNote.title = 'CandidateEmail:' + System.now();
try{
// Noteレコード格納
insert cNote;

// DB例外発生時処理
} catch (DMLException e){
// Error_Logレコードに書き込み
Error_Log__c log = new Error_Log__c();
log.trace__c = e.getTypeName() + '\n'+
e.getCause() + '\n' +
e.getMessage() +
'\n\nfieldMap=' + fieldMap +
'\n\nmyErr=' + myErr;
insert log;

// 戻り値の成功フラグを偽にして返却
result.success = false;
return result;
}
}

// バイナリorテキスト添付ファイル群を格納するリストを
// 新規生成
List<Attachment> attachments = new List<Attachment>();
// バイナリ添付ファイル群が存在する場合
if (email.binaryAttachments != null){
// バイナリ添付ファイルデータループ
for (Messaging.InboundEmail.BinaryAttachment
emailAttachment:
email.binaryAttachments){

// 新規添付ファイル格納オブジェクト生成
Attachment attachment =
new Attachment();
// 親IDとして応募者レコードを指定
attachment.parentId = candidate.id;
// データとしてそのまま格納
attachment.body = emailAttachment.body;
// ファイル名を指定
attachment.name =
emailAttachment.fileName;
// リストへ追加
attachments.add(attachment);
}
}

// テキスト添付ファイル群が存在する場合
if (email.textAttachments != null){
// テキスト添付ファイルデータループ
for (Messaging.InboundEmail.TextAttachment
emailAttachment:email.textAttachments){
// 新規添付ファイル格納オブジェクト生成
Attachment attachment =
new Attachment();
// 親IDとして応募者レコードを指定
attachment.parentId = candidate.id;
// データとしてBLOB型化して格納
attachment.body =
blob.valueOf(
emailAttachment.body);
// ファイル名を指定
attachment.name =
emailAttachment.fileName;
// リストへ追加
attachments.add(attachment);
}
}

// 格納対象となる添付ファイルが存在する場合
if (attachments.size() > 0){
try{
// 添付ファイル新規追加
insert attachments;

// DB例外発生時処理
} catch (DMLException e){
// Error_Logレコードに例外情報を格納
Error_Log__c log = new Error_Log__c();
log.trace__c = e.getTypeName() + '\n' +
e.getCause() + '\n' +
e.getMessage() +
'\n\nfieldMap=' + fieldMap +
'\n\nmyErr=' + myErr;
insert log;
// 戻り値の成功フラグを偽にして返却
result.success = false;
return result;
}
}

// DB以外の例外が発生した場合
} catch (Exception e){
// Error_Logレコードに例外情報書き込み
Error_Log__c log = new Error_Log__c();
log.trace__c = e.getTypeName() + '\n' + e.getCause() +
'\n' + e.getMessage() + '\n\nmyErr=' + myErr;
insert log;
// 戻り値の成功フラグを偽にする
result.success = false;
// エラーメッセージも戻り値オブジェクトへ記述
result.message = e.getTypeName() + '\n' +
e.getCause() + '\n' + e.getMessage() +
'\n\nmyErr=' + myErr;
// 戻り値オブジェクト返却
return result;
}
// 正常終了の場合、生成したままの戻り値オブジェクトを返却
return result;
}
}





やはりメールアドレスを自由に設定できない点が

ちょっとイヤではあるが

外部メールサーバなしで出来る利点がある。

0 件のコメント:

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...