記事公開日

メールサービス機能を利用してメール経由のデータ登録を行う

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&#91;i&#93;); resultValue += (rows&#91;i&#93; + '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; } }
&#91;/java&#93;
<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
画像はgmailでの例です。 メールを送るだけなのでgmail以外も利用できます

画像はGmailでの例です。メールを送るだけなのでGmail以外も利用できます。

※今回作成したプログラムでは本文が上記のようなフォーマットであること前提に解析処理が行われるようにしています。

メール送信後、Salesforce上のリードデータを確認すると、正常に登録されていることが確認できます。

メール経由から登録ができました!

メール経由から登録ができました!

注意事項など

メールサービス機能を利用する上での注意事項を列挙します

  • ガバナ制限:
    メールサービスにもこの制限があります。組織単位で24時間以内の利用可能数が決められており、その数値は「ユーザライセンス数 x 1,000」だそうです。
  • 処理失敗時の挙動:
    メールサービスの処理で何らかのエラーが発生した場合、「Messaging.InboundEmailresult」クラスにエラー文言を設定して success 変数を false にすることで、エラー内容をメールで通知することができます。このときの通知先はメールサービスへのメールの送り主(from)となります(reply-toを指定している場合はそちらに送られる)。
  • 応用:
    この仕組みを応用すればメール経由で「メモ & 添付ファイル」にデータを登録したりすることも可能かと思います!ぜひご活用ください。

ライター情報

CRMのコンサルタントとして、お客さまの業務課題解決のため、BtoBマーケティングやCRMの支援・運用サポート、システム管理者の教育まで幅広い知識を持って提案。