Twilio Programmable Faxで今風なFAX送受信体験をしてみる

2020.09.07

気づけば最後にブログを書いてから1年近くが過ぎてしまっておりました。 久々のブログはFaxについて書いてみたいと思います。

Faxといえば古いイメージのものではありますが、ビジネス現場では受注処理などで活躍しております。 なのでFAXとその周辺システムが存在するわけですが、クラウド活用が盛んな現在においてはオンプレではなくクラウドで運用してみたくなります。 そんな時の検討候補になるかと思い、触ってみました。

Previously, fax was offline and brittle. Today, it’s online and ready to work.

https://jp.twilio.com/docs/fax より引用

とのことです。

事前準備

電話番号の取得

FAXの送受信ができる電話番号を取得しておきます。 今回は動作確認ができれば良いので、米国の番号を取得しました。

検索条件に下記を指定して検索します。

  • 国を米国
  • faxにチェックをつける

一覧から購入する番号を選択します。

購入します。

サンプルアプリケーションの作成

セットアップ

今回はLaravel, Twilio PHP SDKを使用します。 具体的な手順は割愛いたします。

  • Laravel 6.x インストール

https://readouble.com/laravel/6.x/ja/installation.html

  • PHP開発環境のセットアップ方法

https://jp.twilio.com/docs/usage/tutorials/how-to-set-up-your-php-development-environment

Laravelプロジェクトを作ったあとに、Twilio PHP SDKをインストールしておきます。

  • ngrokのインストール

https://ngrok.com/docs

ローカルで動かしているアプリケーションにTwilioがアクセスできるように、ngrokを使用します。

処理の流れ

自分から自分へFAX送信して、それを受信しております。

  1. FAX送信する内容を作成する(画面入力など)・・・割愛してます
  2. 送信内容をPDFにする・・・割愛してます
  3. FAXを送信する(TwilioのFAXリソースを作成する)
  4. FAXを受信する①(Twilioからのwebhookを受ける&TwiMLで受信処理を開始する)
  5. FAXを受信する②(受信処理完了)
  6. 受信したFAXを確認する

実際の運用では自分から自分へFAX送信することは無いと思いますが、今回は送信すると間も無くFAXが届くという動きをします。

コントローラーの作成

FAX送信と受信を試せれば良いので、送信はエンドポイントを直接呼び出して送信先、送信元、送信対象のPDFといった内容をベタ書きしました。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Twilio\Rest\Client;

class FaxController extends Controller
{
    private $twilio;

    public function __construct()
    {
        $sid = config('app.twilio_sid');
        $token = config('app.twilio_token');
        $this->twilio = new Client($sid, $token);
    }

FAXリソースを作成するとFAXが送信されます。 指定するパラメーターは、送信先の電話番号、送信元の電話番号、送信する内容(PDFファイルのURLを指定)なのでわかりやすいです。

    // Faxを送信する
    public function createFaxResource()
    {
        // Faxの送信先(自分へ)
        $to = {{電話番号をE.164形式で設定}};
        
        // Fax送信するPDF
        $mediaUrl = {{FAX送信するPDFのURLを指定}};
        
        // Faxの送信元(自分から)
        $from = {{電話番号をE.164形式で設定}};

        // Faxを送信する
        $fax = $this->twilio->fax->v1->faxes->create($to, $mediaUrl, array('from' => $from));

        // 作成されたFaxリソースの内容をログしておく
        logger()->notice('Create Fax Resource', [
            'sid' => $fax->sid,
            'accountSid' => $fax->accountSid,
            'from' => $fax->from,
            'to' => $fax->to,
            'quality' => $fax->quality,
            'mediaSid' => $fax->mediaSid,
            'mediaUrl' => $fax->mediaUrl,
            'numPages' => $fax->numPages,
            'duration' => $fax->duration,
            'status' => $fax->status,
            'direction' => $fax->direction,
            'apiVersion' => $fax->apiVersion,
            'price' => $fax->price,
            'priceUnit' => $fax->priceUnit,
            'dateCreated' => $fax->dateCreated,
            'dateUpdated' => $fax->dateUpdated,
            'links' => $fax->links,
            'url' => $fax->url,
        ]);

        // Faxリソースのsidを表示(処理結果の確認用)
        print $fax->sid;
    }

送受信したFAXの内容を確認するためのものです。 今回はFAXリソースの内容をログ書き込みしております。

    // Faxリソースの取得
    public function fetchFaxResource($sid)
    {
        // Faxリソースの取得
        $fax = $this->twilio->fax->v1->faxes($sid)->fetch();

        // 作成したFaxリソースの結果をログする
        logger()->notice('Fetch Fax Resource', [
            'sid' => $fax->sid,
            'accountSid' => $fax->accountSid,
            'from' => $fax->from,
            'to' => $fax->to,
            'quality' => $fax->quality,
            'mediaSid' => $fax->mediaSid,
            'mediaUrl' => $fax->mediaUrl,
            'numPages' => $fax->numPages,
            'duration' => $fax->duration,
            'status' => $fax->status,
            'direction' => $fax->direction,
            'apiVersion' => $fax->apiVersion,
            'price' => $fax->price,
            'priceUnit' => $fax->priceUnit,
            'dateCreated' => $fax->dateCreated,
            'dateUpdated' => $fax->dateUpdated,
            'links' => $fax->links,
            'url' => $fax->url,
        ]);
    }

TwilioからのFAX受信イベントを受けます。 POSTパラメーターに送信元、送信先の電話番号が含まれておりますので、どこからきたFAXなのか?といったことがわかります。

    // Fax受信イベントのコールバック(受信開始)
    public function faxSent(Request $request)
    {
        // パラメーターをログする
        logger()->notice('Fax Incoming!', [
            'FaxSid' => $request->input('FaxSid'),
            'AccountSid' => $request->input('AccountSid'),
            'From' => $request->input('From'),
            'To' => $request->input('To'),
            'RemoteStationId' => $request->input('RemoteStationId'),
            'FaxStatus' => $request->input('FaxStatus'),
            'ApiVersion' => $request->input('ApiVersion'),
            'NumPages' => $request->input('NumPages'),
            'MediaUrl' => $request->input('MediaUrl'),
            'ErrorCode' => $request->input('ErrorCode'),
            'ErrorMessage' => $request->input('ErrorMessage'),
        ]);

        // TwiMLへのレスポンス作成
        $twimlResponse = new \SimpleXMLElement("<Response></Response>");
        $twimlResponse->addChild('Receive')->addAttribute('action', '/faxes/callback/received');

        // TwiMLへのレスポンス
        return response($twimlResponse->asXML())
                ->header('Content-Type', 'text/xml');
    }

1つ上のfaxSentメソッドでTwiMLへFAX受信処理の開始命令を行い、コールバックエンドポイントとして下記メソッドを指定してます。 FAX受信が完了したら、POSTパラメーターの中身をチェックして後続処理を行うことになります。 今回はMediaUrlの値をチェックして受信したPDFファイルを見ることにしました。

    // Fax受信イベントのコールバック(受信完了)
    public function faxReceived(Request $request)
    {
        // パラメーターをログする
        logger()->notice('Fax Received!!!', [
            'FaxSid' => $request->input('FaxSid'),
            'AccountSid' => $request->input('AccountSid'),
            'From' => $request->input('From'),
            'To' => $request->input('To'),
            'RemoteStationId' => $request->input('RemoteStationId'),
            'FaxStatus' => $request->input('FaxStatus'),
            'ApiVersion' => $request->input('ApiVersion'),
            'NumPages' => $request->input('NumPages'),
            'MediaUrl' => $request->input('MediaUrl'),
            'ErrorCode' => $request->input('ErrorCode'),
            'ErrorMessage' => $request->input('ErrorMessage'),
        ]);

        // Faxリソースを取得(ログ用)
        $this->fetchFaxResource($request->input('FaxSid'));

        // TwiMLへのレスポンス
        return response('', 200)
                ->header('Content-Type', 'text/xml');
    }
}

ルーティング設定

各エンドポイントの設定をしておきます。 FAX受信イベントのコールバックはPOSTメソッドでのエンドポイントになりますので、ご注意ください。

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

// Faxの送信
Route::get('/faxes/send', 'FaxController@createFaxResource');

// Faxリソースの取得
Route::get('/faxes/{sid}', 'FaxController@fetchFaxResource');

// Fax受信イベントのコールバック(受信開始)
Route::post('/faxes/callback/sent', 'FaxController@faxSent');

// Fax受信イベントのコールバック(受信完了)
Route::post('/faxes/callback/received', 'FaxController@faxReceived');

あと、CSRFトークンのチェックを外しておきます。

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        '/faxes/callback/sent',
        '/faxes/callback/received',
    ];
}

Twilioのwebhook設定

取得した電話番号でFaxのWebhookイベントを受けられるようにします。

こちらへアクセスし、使用する電話番号を選択します。

https://jp.twilio.com/console/phone-numbers/incoming

Voice & Faxの設定をします。

ACCEPT INCOMINGFaxes にします。 ここで注意なのは、 Faxes にすることで、この電話番号はFAX用になります。 電話のwebhookイベントを受けるには別に電話番号を取得する必要があります。

A FAX COMES IN にFAX受信のコールバックエンドポイントを指定します。今回はngrokの方のURLになります。

設定したら save ボタンを押して保存します。

動かしてみる

FAXの送信

今回は送信のエンドポイントをたたくだけです。 送信用にExcelをPDF化して下記を用意しました。

作成されたFAXリソースをみると、このようになっております。

{
	"sid":"FX19ab04e***************9205636b", ←FAX送信処理を特定するID
	"accountSid":"***************",
	"from":"+1***************", ←送信元
	"to":"+1***************", ←送信先
	"quality":"fine", ←送信画質(今回はデフォルト)
	"mediaSid":null,
	"mediaUrl":null,
	"numPages":null,
	"duration":null,
	"status":"queued", ←処理ステータスはキューに入った状態
	"direction":"outbound", ←送信
	"apiVersion":"v1",
	"price":null,
	"priceUnit":null,
	"dateCreated":"2020-09-06 19:37:32",
	"dateUpdated":"2020-09-06 19:37:32",
	"links":{
		"media":"https://fax.twilio.com/v1/Faxes/FX19ab04e***************9205636b/Media"
	},
	"url":"https://fax.twilio.com/v1/Faxes/FX19ab04e***************9205636b"
}

FAXの受信(受信開始)

自分に送ったFAXが届きました。

{
	"FaxSid":"FX90541***************906baefe", ←FAX受信処理を特定するID
	"AccountSid":"***************",
	"from":"+1***************", ←送信元
	"to":"+1***************", ←送信先
	"RemoteStationId":null,
	"FaxStatus":null,
	"ApiVersion":"v1",
	"NumPages":null,
	"MediaUrl":null, ←まだ受信完了していないので受信したFAXイメージが無い
	"ErrorCode":null,
	"ErrorMessage":null
}

FAXの受信(受信完了)

FAXの受信処理が完了しました。

{
	"FaxSid":"FX90541***************906baefe", ←FAX受信処理を特定するID(受信開始したFAX)
	"AccountSid":"***************",
	"from":"+1***************", ←送信元
	"to":"+1***************", ←送信先
	"RemoteStationId":"+1***************",
	"FaxStatus":"received", ←受信完了
	"ApiVersion":"v1",
	"NumPages":"1", ←ページ数
	"MediaUrl":"https://media.twiliocdn.com/fax/ACcd72d400(略)", ←署名付きURL
	"ErrorCode":null,
	"ErrorMessage":null
}

受信完了したFAXリソースの内容も確認しました。 受信完了イベントにMediaUrlなど、受信FAXを確認するための情報は揃っていると思います。

{
	"sid":"FX90541***************906baefe", ←FAX受信処理を特定するID(受信開始したFAX)
	"AccountSid":"***************",
	"from":"+1***************", ←送信元
	"to":"+1***************", ←送信先
	"quality":"fine",
	"mediaSid":"ME4c4c***************c6",
	"mediaUrl":"https://media.twiliocdn.com/fax/ACcd72d400(略)", ←署名付きURL
	"numPages":1, ←ページ数
	"duration":22,
	"status":"received", ←受信完了
	"direction":"inbound", ←受信
	"apiVersion":"v1",
	"price":"-0.48209",
	"priceUnit":"JPY",
	"dateCreated":"2020-09-06 19:37:34",
	"dateUpdated":"2020-09-06 19:37:57",
	"links":{
		"media":"https://fax.twilio.com/v1/Faxes/FX90541******************/Media"
	},
	"url":"https://fax.twilio.com/v1/Faxes/FX90541******************"
}

MediaUrlは署名付きURLになっております。 アクセスするとこんな感じで受信したFAXを確認できます。

おわりに

今回はとりあえず送受信してみたのと、それに伴ってどのような情報を取れるのかを追ってみました。 実際に導入する場合は、いろいろと検討しないといけないことがあると思いますので、今後また書いてみたいと思います。

補足

マニュアルによりますと、現状ではFAX受信の性能面において不安定な部分があるため、ベータ版の扱いとのことです。

https://jp.twilio.com/docs/fax/receive

Update on Fax Beta

We have received feedback from our customers about inconsistent performance when receiving Faxes on Twilio numbers. We have taken this feedback seriously and are actively working to improve the Fax product. We want to make sure that the product we release meets the quality and standards that our customers expect from us.