- 記事公開日
メールサービス機能を利用してメール経由のデータ登録を行う
Salesforceには標準で「Web-to-リード」や「メール-to-ケース」など数々のチャネルからデータを取り込む手段が用意されています。中でもメール-to-ケースは、メールという親しみ深いものを利用しており、手軽に使える機能であるため、利用している方もおられるかと思います。また、メール-to-ケースではケースオブジェクトのみを登録の対象としていますが、他のオブジェクト(例:取引先責任者やリード)についても同じようにメールから登録したいという要望もあるかと思います。
今回は上記要望をかなえるために、Salesforceの「メールサービス機能」を使った、簡単なリードデータ登録プログラム(いわゆる「メール-to-リード」!)を紹介します。
はじめに
- 本記事ではメールサービス機能に関して紹介していますが、一部Apexコードについても触れている箇所があります。あらかじめご了承ください。
- 本記事にて紹介しているApexコードはあくまでもサンプルコードとなります。運用組織上で使用する場合には、十分に動作確認などをおこなったうえでご利用ください。
メールサービス機能について
簡単に説明すると、メール受信のイベントをきっかけにあらかじめ指定したApexコードが起動し、受信したメールの解析や内容に応じたアクション(データ登録など)を実行することができる機能です。
メール受信時に起動させるApexクラスは必ず「Messaging.InboundEmailHandler」インターフェイスを実装しておかなければなりません。
リードデータを登録するメールサービスの作成方法
メールサービス機能を利用した、リードデータ登録機能の作成方法を紹介します。
手順1. Apexクラスを作成
作成するクラスは
- メール本文解析用クラス
- メールサービス用クラス
(Messaging.InboundEmailHandler インターフェイス実装クラス)
の2つです(クラスを2つに分けているのは、少しの変更でリードデータ登録以外にも対応しやすくするためです)。
メール本文解析用クラス ソースコード
public class ConverterToSObjectFromMailbody {
public static final Integer MAX_MAILBODY_LENGTH = 50000; //メール本文の最大許容文字数
public static final Integer MAX_MAILBODY_NUMOFLINE = 1000; //メール本文の最大許容行数
private static final String REG_KEYWORD = '\[.*\]';
private static final Pattern PTN_KEYWORD = Pattern.compile(REG_KEYWORD);
public Boolean throwInvalidField = false; //これをtrueにすると項目の変換に失敗した場合でも例外を投げる
private String objectName;
@TestVisible
private Map<String, FieldInfo> fieldMap;
public ConverterToSObjectFromMailbody(String objectName){
this.objectName = objectName;
this.fieldMap = getFieldMap();
}
public class ConvertException extends Exception{
}
public class KeyWordInfo{
public String word{ get; set; }
public Integer num{ get; set; }
}
public class FieldInfo{
public String name{ get; set; }
public DisplayType type{ get; set; }
}
public SObject parseMailBodyToSObject( List<String> fieldNames , String body ){
if(body.length() > MAX_MAILBODY_LENGTH){
throw new ConvertException(String.format('本文文字数が {0} を越えています' , new String[]{String.valueOf(MAX_MAILBODY_LENGTH)} ));
}
List<String> rows = body.split('n');
if(rows.size() > MAX_MAILBODY_NUMOFLINE){
throw new ConvertException(String.format('本文行数が {0} を越えています' , new String[]{String.valueOf(MAX_MAILBODY_NUMOFLINE)} ));
}
Set<String> keywordSet = new Set<String>();
Map<String, FieldInfo> keywordMap = new Map<String, FieldInfo>();
for(String n : fieldNames){
FieldInfo f = fieldMap.get(n);
if(f != null){
String keyword = '[' + n + ']';
system.debug('keyword : ' + keyword);
keywordSet.add( keyword );
keywordMap.put( keyword , f );
}
}
KeyWordInfo prevKeyword = null;
KeyWordInfo nextKeyword = null;
SObject resultSObject = createDefaultSObject();
prevKeyword = findKeyword( keywordSet , rows , 0);
if(prevKeyword.num < 0){ return resultSObject; } while(true){ keywordSet.remove( prevKeyword.word ); nextKeyword = findKeyword( keywordSet , rows , prevKeyword.num+1); FieldInfo f = keywordMap.get(prevKeyword.word); system.debug('prev : '+ prevKeyword); system.debug('next : '+ nextKeyword); String value = findValue(prevKeyword.num+1 , nextKeyword.num-1 , rows); Object realValue = convertValue( value , f ); if(realValue != null || f.type != DisplayType.Boolean){ //チェックボックス型の項目を明示的にNull指定すると更新時に例外が発生するので設定しないようにする resultSObject.put( f.name , convertValue( value , f )); } if(nextKeyword.num >= rows.size()){
break;
}
prevKeyword = nextKeyword;
}
return resultSObject;
}
private Map<String, FieldInfo> getFieldMap(){
List<SObjectField> fieldList = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().values();
Map<String, FieldInfo> resultMap = new Map<String, FieldInfo>();
for( SObjectField s : fieldList){
DescribeFieldResult fResult = s.getDescribe();
FieldInfo info = new FieldInfo();
info.name = fResult.getName();
info.type = fResult.getType();
resultMap.put( fResult.getName() , info );
}
return resultMap;
}
private SObject createDefaultSObject(){
return Schema.getGlobalDescribe().get(objectName).newSObject() ;
}
private KeyWordInfo findKeyword( Set<String> keywords , List<String> rows , Integer index){
KeyWordInfo k = new KeyWordInfo();
k.num = rows.size();
Integer i = index;
for( ; i < rows.size() ; i++){ String l = rows.get(i); Matcher matcher = PTN_KEYWORD.matcher(l); if( matcher.find() ){ String word = matcher.group(0); //キーワード部分を抽出して、一致しているかを確認 if(keywords.contains( word )){ k.word = word; k.num = i; break; } } } return k; } private String findValue( Integer startIndex , Integer endIndex , List<String> rows ){
if(startIndex > endIndex){
return null;
}
String resultValue = '';
for(Integer i=startIndex ; i<(endIndex+1) ; i++){ system.debug('loop : ' + rows[i]); resultValue += (rows[i] + 'n'); } resultValue = resultValue.subString(0 , resultValue.length() - 1); return resultValue; } private Object convertValue(String value , FieldInfo f ){ if(value == null){ return null; } String message = '対象外のデータ型 Name='+f.name + ' Type='+f.type + ' Value=' + value; DisplayType dType = f.type; if(dType == DisplayType.Double || //小数 dType == DisplayType.Currency|| //通貨 dType == DisplayType.Percent ){ //パーセント try{ Double doubleRes = Double.valueOf(value); return doubleRes; }catch(TypeException e){ message = '小数型の値変換時にエラー Name='+f.name + ' Type='+f.type + ' Value=' + value; } }else if(dType == DisplayType.Integer){ //整数 try{ Integer integerRes = Integer.valueOf(value); return integerRes; }catch(TypeException e){ message = '整数型の値変換時にエラー Name='+f.name + ' Type='+f.type + ' Value=' + value; } }else if( dType == DisplayType.Boolean){ //チェックボックス String lower = value.toLowerCase(); if( lower == 'true' ){ return true; }else if(lower == 'false' ){ return false; } }else if(dType == DisplayType.Date){//日付 //フォーマットが yyyy/mm/dd //であることを前提 try{ Date dateRes = Date.valueOf(value.replaceAll('/','-')); return dateRes; }catch(TypeException e){ message = '日付型の値変換時にエラー Name='+f.name + ' Type='+f.type + ' Value=' + value; } }else if(dType == DisplayType.DateTime){ //日付時間 //フォーマットが yyyy/mm/dd hh:mm:ss //であることを前提 try{ Datetime datetimeRes = Datetime.valueOf(value.replaceAll('/','-')); return datetimeRes; }catch(TypeException e){ message = '日付時間型の値変換時にエラー Name='+f.name + ' Type='+f.type + ' Value=' + value; } }else if( dType == DisplayType.Email || //メール dType == DisplayType.MultiPicklist || //選択リスト(複数) dType == DisplayType.Picklist || //選択リスト dType == DisplayType.Phone || //電話 dType == DisplayType.String || //テキスト dType == DisplayType.TextArea || //テキストエリア dType == DisplayType.URL ){ //URL return value; } if(throwInvalidField){ throw new ConvertException(message); } system.debug( message ); return null; } }
[/java]
<p><strong>メールサービス用クラス ソースコード</strong></p>
[java htmlscript="true"]
global class MailServiceToCreateLead implements Messaging.InboundEmailHandler {
/**
* プログラムのエントリポイント
* メール情報を受け取って開始する
*/
global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
//メール本文に記載される想定の項目
List<String> PARSE_FIELD = new List<String>{
Lead.Company.getDescribe().getName() , //会社名
Lead.LastName.getDescribe().getName() , //姓
Lead.FirstName.getDescribe().getName(), //名
Lead.Phone.getDescribe().getName(), //電話番号
Lead.Email.getDescribe().getName(), //メール
Lead.PostalCode.getDescribe().getName(),//郵便番号
Lead.State.getDescribe().getName(), //都道府県
Lead.City.getDescribe().getName(), //市区郡
Lead.Street.getDescribe().getName(), //町名番地
Lead.LeadSource.getDescribe().getName() //リードソース
};
String body = email.plainTextBody;
result.success = true;
ConverterToSObjectFromMailbody converter = new ConverterToSObjectFromMailbody('Lead');
try{
//メール本文を解析してリードオブジェクトのインスタンスを生成
Lead l = (Lead)converter.parseMailBodyToSObject( PARSE_FIELD , body);
//insert
insert l;
}catch(Exception e){
//何かしら例外発生時の通知文面
//受信したメールの「reply-to」に指定したアドレスに対して送信される(未指定の場合はfrom)
result.message = 'メールサービス処理でエラーが発生しました。n';
result.message += 'n';
result.message += '【元メール】n';
result.message += body ;
result.message += 'n';
result.message += '【エラーメッセージ】n';
result.message += e.getMessage();
result.success = false;
}
return result;
}
}
必ずメール本文解析用クラス → メールサービス用クラス の順で作成するようにしてください。
手順2. メールサービスを作成
[設定] | [ビルド] | [開発] | [メールサービス] を選択し、[新規メールサービス]をクリック。
メールサービス設定画面に遷移するのでそれぞれ以下の通り設定。
- メールサービス名:
→ ”任意の名称” - Apexクラス:
→ ”メールサービス用クラス” - 有効:
→ チェックを入れる
※他の設定に関しては、環境に合わせて適宜変更してください(詳細)。
設定が完了したら、[保存 & 新規メールアドレス]をクリック。
メールサービス用アドレスの設定画面に遷移するので、メールアドレスのローカル部分(@よりも前の部分)とメールサービス実行時のユーザを指定します(詳細)。
設定が完了したら、[保存]をクリック。
作成したメールサービスの確認画面に遷移します。自動で作成されるメールサービス受信用のアドレスもこの画面に表示されます。
手順3. 動作確認
ここまでの手順でメールサービスについては作成完了しました。せっかくですので動作確認してみましょう。
以下の内容でメールを作成します。
- 宛先
手順2 で自動作成されたメールアドレス - 件名
任意(なんでもよい) - 本文
[Company] //会社名
シナジー
[LastName] //姓
テスト
[FirstName] //名
太郎
[Phone] //電話番号
06-8888-9999
[Email] //メール
test@synergy202.jp
[PostalCode] //郵便番号
5300003
[State] //都道府県
大阪府
[City] //市区郡
大阪市
[Street] //町名番地
北区堂島
[LeadSource] //リードソース
Web
※今回作成したプログラムでは本文が上記のようなフォーマットであること前提に解析処理が行われるようにしています。
メール送信後、Salesforce上のリードデータを確認すると、正常に登録されていることが確認できます。
注意事項など
メールサービス機能を利用する上での注意事項を列挙します
- ガバナ制限:
メールサービスにもこの制限があります。組織単位で24時間以内の利用可能数が決められており、その数値は「ユーザライセンス数 x 1,000」だそうです。 - 処理失敗時の挙動:
メールサービスの処理で何らかのエラーが発生した場合、「Messaging.InboundEmailresult」クラスにエラー文言を設定して success 変数を false にすることで、エラー内容をメールで通知することができます。このときの通知先はメールサービスへのメールの送り主(from)となります(reply-toを指定している場合はそちらに送られる)。 - 応用:
この仕組みを応用すればメール経由で「メモ & 添付ファイル」にデータを登録したりすることも可能かと思います!ぜひご活用ください。
ライター情報
CRMのコンサルタントとして、お客さまの業務課題解決のため、BtoBマーケティングやCRMの支援・運用サポート、システム管理者の教育まで幅広い知識を持って提案。





