MattermostでSlackのメール連携みたいなことがしたい

>>「大人の事情でSlackとかHipChatは使えない」
>
>って話、やっぱり多いみたいなので。世知辛いですね。
―― [SlackクローンのMattermostを使ってみる - 導入、初期設定編-](http://qiita.com/terukizm/items/4a4016d8ec5a21856e4f) ( [@terukizm さん](http://qiita.com/terukizm) )より

世知辛いですね……。
僕の所属組織では現在Slackが使えているのですが、この先雲行きが怪しいので、オンプレでインストールできるMattermostをインストールしてみました。

見た目以外の部分でもhubotなども使え、かなり「Slackっぽい」なあと思いました。
しかし、現在僕がSlackで活用している中で、デフォルトで用意されていない Email integration を(大した労力をかけないで)どうにか使えないかなーと思って思い付きを実行したところ、思いのほかさくっとできたのでご参考までに共有しておこうかと思います。

#概要
下記のような流れになります。

1. 特定のデリミタ以降にIncoming webhooks URLのパラメータ部を含んだメールを送信
2. 上記メールを受信したらスクリプトを起動するようにMTAを設定
3. スクリプトでメールの内容をJSONでMessage Attachments用に成形してWebhookにPOSTする

hubotに激しく手を入れたりしなきゃいけないかなー面倒だなーと思っていましたがとても簡単にできてしまいました。

#具体的な手順

##mattermostでIncoming webhookを用意

自分のユーザアイコンの右のハンバーガーメニューから、Account Settings → General Settings を開きます。
Integrations 中、Incoming Webhooksで、メールを受け取りたいチャンネルを選択して Add ボタンを押します。
WebhooksのURLが生成されますので、「hooks/」より後ろの部分をメモっておきましょう。

![integration_settings.png](https://qiita-image-store.s3.amazonaws.com/0/43970/c1c489d0-a41f-ecec-2d38-d62045cdfadc.png)

##MTAの設定

特定の文字列+特定のデリミタ以降の文字列で届いたメールを、すべて単一のスクリプトに処理させるよう設定します。例えば `bot+y6dkewhqbbgmmrfx8ojsncw9by@my.domain.name` 宛のメールをエイリアス「bot」に設定してあるスクリプトが処理するような感じです。

gmailに倣ってデリミタは「+」にすることにしました。
僕はcentosでpostfixを使っているのでmain.cfの末尾に下記のように設定を加え、systemctl restart  postfix.service とかします。
これは「+以降を無視する」「一度のSMTPセッションでのあて先は1つのみ受け付ける」事を意味します。

```main.cf(抜粋)
recipient_delimiter = +
transport_destination_recipient_limit = 1
```

また、aliases に下記のようなエントリを追加して、newaliases しました。

```aliases(抜粋)
bot: "| /opt/emailbot/mail2channel"
```

これでMTA側の準備は完了です。

##メールを受け取ってwebhookにPOSTするスクリプト

下記のようなほとんどベタ書きのしょぼいスクリプトを書きました。
これを、前節でaliasに設定したパスに設置します。

```perl:mail2channel
#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Email::MIME;
use JSON;

$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

my $curl     = '/usr/bin/curl -s --data-urlencode';
my $hooks    = 'http://my.domain.name/hooks/';
my $username = 'emailbot',
my $iconurl  = 'http://my.domain.name/icons/mail.png',
my $color    = '#439FE0',

my $rawmail = join '', <STDIN>;

my $mail = &parse_mail($rawmail);

my $token = $$mail{rcpt};
$token =~ s/^bot\+(.+)@.*$/$1/ or die;
my $posturl = $hooks . $token ;

my %json= (
  'username'    => "$username",
  'icon_url'    => "$iconurl",
  'attachments' => [
    {
      "color"   => "$color",
      "pretext" => "「$$mail{from}」からメールが届きました。",
      "title"   => "$$mail{subj}",
      "text"    => "$$mail{body}"
    }
  ]
);

my $payload = &escape_html( 'payload=' . encode_json( \%json ) );

my $cmd=$curl . " '" . $payload . "' " . $posturl;

system( "$cmd" );

exit;

sub parse_mail {
  my ($rawmail) = @_;
  my $parsedmail = Email::MIME->new($rawmail);
  my %hash = (
    "rcpt" => scalar($parsedmail->header('Delivered-To')),
    "from" => scalar($parsedmail->header('From')),
    "subj" => scalar($parsedmail->header('Subject')),
    "body" => $parsedmail->body_str
  );
  $hash{body} =~ s/\r\n?|\n\r?/\n/g;
  return(\%hash);
}

sub escape_html {
  my ($str) = @_;
  $str =~ s/&/&amp;/g;
  $str =~ s/</&lt;/g;
  $str =~ s/>/&gt;/g;
  $str =~ s/'/&#39;/g;
  return $str;
}
```

先頭のほうにある変数群を書き換えたり、アイコンURLで指定しているアイコンを実際に設置したりなど適宜調整してください。
これにより、bot+webhookのトークン@ドメイン 宛にメールを送るとIncoming Webhook作成時に指定したチャンネルに通知されるようになりました。

###注意点など

気を付けなければいけないのは、MLから送られてくるメールなど、メールヘッダのTo:は必ずしもBot向けのあて先になっていないという点です。
僕はアラートメールを受信しているMLのメンバーにbotを入れているのでまさにこれに当たるのですが、エンベロープToを拾うようにすればこの問題は回避できます。

なお、postfixではエンベロープToをもとに最終決定された宛先（ローカル配送などのときに自ドメインを補ってくれるあて先）が Delivered-To に記録されるので、このヘッダを使うことにしました。
わざわざ本文ヘッダでDelivered-Toを追加した場合でも、付与されるのはpostfixが追加するヘッダより後になり、「@.*$」に最長マッチする部分に含まれるため問題ないはずです。


##メールを送信する

実際にメールを送ってみます。

![mail.png](https://qiita-image-store.s3.amazonaws.com/0/43970/322eba59-4fe0-bd89-a846-4c1d2084406b.png)

すると、目的のチャンネルで下記のように Message Attachments でそれらしく表示されました。

![result.png](https://qiita-image-store.s3.amazonaws.com/0/43970/0da01e4e-c056-5f61-eda2-135e817edc66.png)

5行以上の本文テキストは折り畳まれます（この折り畳まれる行数を変更する設定箇所を探しているのですが、現時点では見当たっていません）。

本件は以上となります。
とりあえずMattermost本家でEmail Integrationが実装されるまでのつなぎとして、普段使いには必要十分だと思います。皆さまの世知辛いMattermostライフの一助になりましたら幸いです。