Twilio FunctionでFAX受信

Twilio Functionsを使ってFAX受信
---

# はじめに

FAX、使ってます？個人利用だと特定用途以外は電子メールに置き換わってきましたが、業務ではまだまだ現役でFAXが動いていて商品の受発注の紙書類をやりとりしてることも多いかと思います。

FAXを駆逐したいと思いつつも、日常の業務フローを崩したくないといった悩みを、Twilioの各種機能を使ってやんわりとFAXがなくてもいい状態にしていきたいところ。

そんな悩みを、Twilio FunctionsとTwiML binsを使って、受信したTwilio FAXをメールとSlackに流すことで解消していきます。

## 理想像

![image.png](https://qiita-image-store.s3.amazonaws.com/0/38845/244272df-d98a-924c-78de-91342678e82e.png)

- FAXを受信して、受信したデータPDFをメールアドレス＆Slackに送ります。
- FAXの送信、専用用紙でFAX送信することを考えて既存の機械を使用します   
(FAX送信は、 [Twilio Advent Calendar 2017](https://qiita.com/advent-calendar/2017/twilio)の24日目に記事が公開されるようです)


## 必要なもの
- Twilioアカウント
    - FAXが受信できる電話番号
    - FAX用にサブアカウントまたはプロジェクトをしたほうが後々の管理が楽になります。
- メールアカウント
    - メール送信できるSMTP関連情報
- slackアカウント

# 準備
## メールアドレス
SMTPサーバーでメール送信できるアカウントを用意してください。

- 例：Amazon SES


## Slack
slack incoming webhooks

- App作成
  ファイルをSlackへuploadするため、App作成が必要です。


## Twilio
- サブアカウントの作成
- Twilio FunctionsのConfigurationの設定
    - Credentials   
        Enable ACCOUNT_SID and AUTH_TOKEN : OFF
    - Environmental Variables
        - FaxMailSmtpServer  …… メール送信時のSMTPサーバのアドレス
        - FaxMailSmtpPass …… メール送信時のSMTPアカウントのID
        - FaxMailSmtpUser …… メール送信時のSMTPアカウントのパスワード
        - FaxMailFromAddress …… メール送信時の送信**元**メールアドレス
        - FaxMailToAddress …… メール送信時の送信**先**メールアドレス
        - FaxSlackAccessToken …… Slack投稿時のAccessToken
        - FaxSlackChannel …… Slack投稿時の#チャンネル
    - Dependencies
        - nodemailer:4.4.0 …… メール送信用に使います
        - request-promise:4.2.2 …… 外部REST APIを呼び出すために使います

# 設定
## Twilio Functions
```js
exports.handler = function(context, event, callback) {

    console.log("event:",event);

    if ("received" != event.FaxStatus) {
        console.log("not fax?");
        console.log("FaxStatus:" + event.FaxStatus);
        console.log("ErrorCode:" + event.ErrorCode);
        console.log("From:" + event.From);
        callback(null, "not fax?");
    }
    else {
        sendMailPdf(event.MediaUrl);
        postSlack(event.MediaUrl);
    }

    /*** sendmail ***/
    function sendMailPdf(url) {
        let nodemailer = require('nodemailer');

        let transporter = nodemailer.createTransport({
            host: context.FaxMailSmtpServer,
            port: 587, // ※使用するSMTPサーバに応じて変更してください
            secure: false,
            auth: {
                user: context.FaxMailSmtpUser,
                pass: context.FaxMailSmtpPass
            }
        });

        let mailOptions = {
            from: context.FaxMailFromAddress,
            to: context.FaxMailToAddress,
            subject: 'Fax received, from:' + event.From,
            text: 'Fax received, from:' + event.From,
            attachments: [{
                filename: 'fax.pdf',
                path: url
            }]
        };

        transporter.sendMail(mailOptions, (error, info) => {
            if (error) {
                console.log(error);
            }
            else {
                console.log('Mail Message sent: %s', info.messageId);
            }
        });
    }

    /*** post to slack ***/
    function postSlack(urlpdf) {
        let rp = require('request-promise');
        let fs = require('fs');
        let util = require('util');

        let optionpdf = {
            method: 'GET',
            uri: urlpdf,
            encoding: null
        };

        let tmpfile = util.format("/tmp/%s.pdf", Math.floor(10000 * 10000 * Math.random()).toString(16));
        console.log("tmpfile:", tmpfile);

        rp(optionpdf)
            .then(function(parsedBodyA) {
                fs.writeFileSync(tmpfile, parsedBodyA);
            })
            .catch(function(errA) {
                console.log("Ae:", errA);
            })
            .finally(function() {
                // pdf download, success
                let optionslack = {
                    method: 'POST',
                    uri: "https://slack.com/api/files.upload",
                    formData: {
                        token: context.FaxSlackAccessToken,
                        file: fs.createReadStream(tmpfile),
                        filetype: "application/pdf",
                        channels: context.FaxSlackChannel,
                      	initial_comment: 'Fax received, from:' + event.From
                    }
                };

                rp(optionslack)
                    .then(function(parsedBodyB) {
                        console.log("Bs:", parsedBodyB);
                    })
                    .catch(function(errB) {
                        console.log("Be:", errB);
                    })
                    .finally(function() {
                        fs.unlink(tmpfile, function(errC) {
                            console.log("Bf:", errC);
                        });
                        console.log("erase temfile:", tmpfile);
                    });
            });
    }
};
```



## Twilio Bins
Twilio FAXを受信(着呼)したときの動作を設定します。

actionのところはTwilio Functionsで生成されたURLに書き換えてください。最初はFunctionsで<Response>を記載しようと思ったのですが、Functionsで<Response>を書くと正しく動作しないことも有りました。よくよく考えてみれば、<Response>は受信したときにココのURLを呼び出して下さいと固定のTwiMLを返せばいいだけなので、TwilioのTwiML Binsで対応すればいいことに気づき、Functionsを使わずにTwiML Binsで構成しました。


```xml:fax.incoming
<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <Receive action="https://ceaseless-trees-XXXX.twil.io/fax.received" />
</Response>
```

## 電話番号設定
- FAXが受信設定できる電話番号を取得します
- ACCEPT INCOMING：「Faxes」を選択
- CONFIGURE WITH：「Webhooks, TwiML Bins, Functions, Studio, or Proxy」を選択
- A FAX COMES IN：作成したTwilio Binsを選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/38845/d9977907-1362-5232-a4d7-c989dff09b76.png)

# 最後に
駆け足で作ってみました。一部、Functionsに記載したコードが納得できてない部分があるので、これから調整します。最新版のソースコードは、[gist](https://gist.github.com/yyano/7c4b2ce15023c6c71ed555c78bcd275e)に掲載していきます。また、適宜 解説を追加をしていこうと思っています。

## わかっている不具合
(2017/12/19時点)FAX機器の相性の問題だとは思うのですが、一部のオンライン系のFAXサービス(eFax)から送ったFAXがTwilio FAXで受信できませんでした。FAX実機(複合機)などでは受信できるようなので、どこの部分が問題なのか切り分けできていませんが、ココに記録しておきます。

## Twilioのサポートについて
このFAX機能および記事を書くにあたり、とても親切に教えていただきました。この場を借りてお礼申し上げます。