メールサービス機能を利用してメール経由のデータ登録を行う
Salesforceには標準で「Web-to-リード」や「メール-to-ケース」など数々のチャネルからデータを取り込む手段が用意されています。中でもメール-to-ケースは、メールという親しみ深いものを利用しており、手軽に使える機能であるため、利用している方もおられるかと思います。また、メール-to-ケースではケースオブジェクトのみを登録の対象としていますが、他のオブジェクト(例:取引先責任者やリード)についても同じようにメールから登録したいという要望もあるかと思います。
今回は上記要望をかなえるために、Salesforceの「メールサービス機能」を使った、簡単なリードデータ登録プログラム(いわゆる「メール-to-リード」!)を紹介します。
はじめに
- 本記事ではメールサービス機能に関して紹介していますが、一部Apexコードについても触れている箇所があります。あらかじめご了承ください。
- 本記事にて紹介しているApexコードはあくまでもサンプルコードとなります。運用組織上で使用する場合には、十分に動作確認などをおこなったうえでご利用ください。
メールサービス機能について
簡単に説明すると、メール受信のイベントをきっかけにあらかじめ指定したApexコードが起動し、受信したメールの解析や内容に応じたアクション(データ登録など)を実行することができる機能です。
メール受信時に起動させるApexクラスは必ず「Messaging.InboundEmailHandler」インターフェイスを実装しておかなければなりません。
リードデータを登録するメールサービスの作成方法
メールサービス機能を利用した、リードデータ登録機能の作成方法を紹介します。
手順1. Apexクラスを作成
作成するクラスは
- メール本文解析用クラス
- メールサービス用クラス
(Messaging.InboundEmailHandler インターフェイス実装クラス)
の2つです(クラスを2つに分けているのは、少しの変更でリードデータ登録以外にも対応しやすくするためです)。
メール本文解析用クラス ソースコード
<br /> public class ConverterToSObjectFromMailbody {</p> <p> public static final Integer MAX_MAILBODY_LENGTH = 50000; //メール本文の最大許容文字数<br /> public static final Integer MAX_MAILBODY_NUMOFLINE = 1000; //メール本文の最大許容行数<br /> private static final String REG_KEYWORD = '\\[.*\\]';<br /> private static final Pattern PTN_KEYWORD = Pattern.compile(REG_KEYWORD);</p> <p> public Boolean throwInvalidField = false; //これをtrueにすると項目の変換に失敗した場合でも例外を投げる</p> <p> private String objectName;<br /> @TestVisible<br /> private Map<String, FieldInfo> fieldMap;<br /> public ConverterToSObjectFromMailbody(String objectName){<br /> this.objectName = objectName;<br /> this.fieldMap = getFieldMap();<br /> }</p> <p> public class ConvertException extends Exception{<br /> }</p> <p> public class KeyWordInfo{<br /> public String word{ get; set; }<br /> public Integer num{ get; set; }<br /> }</p> <p> public class FieldInfo{<br /> public String name{ get; set; }<br /> public DisplayType type{ get; set; }<br /> }</p> <p> public SObject parseMailBodyToSObject( List<String> fieldNames , String body ){</p> <p> if(body.length() > MAX_MAILBODY_LENGTH){<br /> throw new ConvertException(String.format('本文文字数が {0} を越えています' , new String[]{String.valueOf(MAX_MAILBODY_LENGTH)} ));<br /> }</p> <p> List<String> rows = body.split('\n');<br /> if(rows.size() > MAX_MAILBODY_NUMOFLINE){<br /> throw new ConvertException(String.format('本文行数が {0} を越えています' , new String[]{String.valueOf(MAX_MAILBODY_NUMOFLINE)} ));<br /> }</p> <p> Set<String> keywordSet = new Set<String>();<br /> Map<String, FieldInfo> keywordMap = new Map<String, FieldInfo>();<br /> for(String n : fieldNames){<br /> FieldInfo f = fieldMap.get(n);<br /> if(f != null){<br /> String keyword = '[' + n + ']';<br /> system.debug('keyword : ' + keyword);<br /> keywordSet.add( keyword );<br /> keywordMap.put( keyword , f );<br /> }<br /> }</p> <p> KeyWordInfo prevKeyword = null;<br /> KeyWordInfo nextKeyword = null;<br /> SObject resultSObject = createDefaultSObject();</p> <p> prevKeyword = findKeyword( keywordSet , rows , 0);<br /> 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()){<br /> break;<br /> }</p> <p> prevKeyword = nextKeyword;</p> <p> }<br /> return resultSObject;</p> <p> }</p> <p> private Map<String, FieldInfo> getFieldMap(){<br /> List<SObjectField> fieldList = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().values();<br /> Map<String, FieldInfo> resultMap = new Map<String, FieldInfo>();<br /> for( SObjectField s : fieldList){<br /> DescribeFieldResult fResult = s.getDescribe();<br /> FieldInfo info = new FieldInfo();<br /> info.name = fResult.getName();<br /> info.type = fResult.getType();<br /> resultMap.put( fResult.getName() , info );<br /> }<br /> return resultMap;<br /> }</p> <p> private SObject createDefaultSObject(){<br /> return Schema.getGlobalDescribe().get(objectName).newSObject() ;<br /> }</p> <p> private KeyWordInfo findKeyword( Set<String> keywords , List<String> rows , Integer index){</p> <p> KeyWordInfo k = new KeyWordInfo();<br /> k.num = rows.size();<br /> Integer i = index;<br /> 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 ){</p> <p> if(startIndex > endIndex){<br /> return null;<br /> }</p> <p> String resultValue = '';<br /> 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; } }
メールサービス用クラス ソースコード
<br /> global class MailServiceToCreateLead implements Messaging.InboundEmailHandler {</p> <p> /**<br /> * プログラムのエントリポイント<br /> * メール情報を受け取って開始する<br /> */<br /> global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {</p> <p> Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();</p> <p> //メール本文に記載される想定の項目<br /> List<String> PARSE_FIELD = new List<String>{<br /> Lead.Company.getDescribe().getName() , //会社名<br /> Lead.LastName.getDescribe().getName() , //姓<br /> Lead.FirstName.getDescribe().getName(), //名<br /> Lead.Phone.getDescribe().getName(), //電話番号<br /> Lead.Email.getDescribe().getName(), //メール<br /> Lead.PostalCode.getDescribe().getName(),//郵便番号<br /> Lead.State.getDescribe().getName(), //都道府県<br /> Lead.City.getDescribe().getName(), //市区郡<br /> Lead.Street.getDescribe().getName(), //町名番地<br /> Lead.LeadSource.getDescribe().getName() //リードソース<br /> };</p> <p> String body = email.plainTextBody;<br /> result.success = true;<br /> ConverterToSObjectFromMailbody converter = new ConverterToSObjectFromMailbody('Lead');<br /> try{<br /> //メール本文を解析してリードオブジェクトのインスタンスを生成<br /> Lead l = (Lead)converter.parseMailBodyToSObject( PARSE_FIELD , body);<br /> //insert<br /> insert l;<br /> }catch(Exception e){<br /> //何かしら例外発生時の通知文面<br /> //受信したメールの「reply-to」に指定したアドレスに対して送信される(未指定の場合はfrom)<br /> result.message = 'メールサービス処理でエラーが発生しました。\n';<br /> result.message += '\n';<br /> result.message += '【元メール】\n';<br /> result.message += body ;<br /> result.message += '\n';<br /> result.message += '【エラーメッセージ】\n';<br /> result.message += e.getMessage();<br /> result.success = false;<br /> }<br /> return result;<br /> }</p> <p>}<br />
必ずメール本文解析用クラス → メールサービス用クラス の順で作成するようにしてください。
手順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を指定している場合はそちらに送られる)。 - 応用:
この仕組みを応用すればメール経由で「メモ & 添付ファイル」にデータを登録したりすることも可能かと思います!ぜひご活用ください。
※記載されている内容は掲載当時のものであり、一部現状とは内容が異なる場合があります。ご了承ください。