💻

Netlify Functions+Maligunでメールを送信する方法

2020/03/11に公開

Netlifyでは、Functionsという機能(AWS Lambdaのラッパー)を使ってサーバーサイドでコードを動かすことができます。

今回は、これを使ってMailgunでのメール送信を行う方法を具体的に紹介してみたいと思います。

Netlifyとは

Netlify(ネットリファイ)は静的サイトを無料からホスティングできるWebサービスです。

本ブログのこちらの記事 でも詳しく説明しているので、よろしければご参照ください。

NetlifyのFunctions機能とは

Netlifyには、Functions というサーバーレスfunctionをホスティングできる機能(AWS Lambdaのラッパー)が付随しており、こちらも無料から利用できます。

無料プランでの利用制限は

  • 月あたり125,000リクエスト
  • 月あたりランタイム100時間

となっています。(料金表はこちら

例えばSPAや静的サイトからMailgunなどのサービスを使ってメール送信を行う場合、APIキーのようなプライベートな情報をフロントエンドのコードに露出させてしまうのはセキュリティ上NGなので、Mailgunとのやりとりだけはサーバーサイドに追いやる必要があります。

このような場合に、Netlify Functionsを使えば、

  • 別途サーバー(やサーバーレス)の構築やセットアップをしなくていい
  • フロントエンドのコードとサーバーサイド(Functions)のコードを1つのコードベースで管理できる

という点でとても便利です👍

Functionsの具体的な使い方(Node.jsの例)

具体的な使い方は以下のとおりです。

  1. コードベースにFunctions用のディレクトリを追加する(例えば /functions
  2. Netlifyの管理画面( https://app.netlify.com/sites/{サイト名}/settings/functions )で、Functions用ディレクトリのパスをセット(参考
  3. 例えばNode.jsでfunctionを書く場合は、Functions用ディレクトリ内に {function名}.js というファイルを作成して、具体的な処理を書く(後述)
  4. デプロイする

この手順を実施すると、functionが https://{サイト名}.netlify.com/.netlify/functions/{function名} にデプロイされます。つまり、フロントエンドから /.netlify/functions/{function名} にリクエストすればfunctionに繋がるわけですね。

{function名}.js の具体的な書き方は 公式リファレンス に説明があります。

Node.jsではなくGoでfunctionを書く場合は こちら をご参照ください。

最小の内容は以下のようなものになります。

exports.handler = function(event, context, callback) {
    return { statusCode: 200, body: 'OK' };
}

引数で受け取っている event オブジェクトの内容は

{
    "path": "Path parameter",
    "httpMethod": "Incoming request's method name"
    "headers": {Incoming request headers}
    "queryStringParameters": {query string parameters }
    "body": "A JSON string of the request payload."
    "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}

このようになっているので、例えばPOSTリクエストしか受け付けたくない場合は

if (event.httpMethod !== 'POST') {
  return { statusCode: 405, body: 'Method Not Allowed' }
}

こんな感じで書けますし、リクエストペイロードのJSONからデータを取り出す場合は

const req = JSON.parse(event.body);
console.log(req);

こんな感じでやればOKです。

これくらい分かればどんな処理でも書けそうですね👍

Functionsをローカルで動かすにはNetlify Devを使う

ところで、開発中のfunctionの動作確認をするためにいちいちデプロイしないといけないのは面倒すぎますよね。

安心してください。ちゃんと開発環境でfunctionを動かす方法が用意されています👌

まだBeta版とのことですが、Netlify Dev という機能です。

使い方はとても簡単で、

# netlify-cliをインストール
$ npm install netlify-cli -g

# netlifyコマンドが使えるようになるので、以下を実行
$ netlify dev

とするだけです。

公式サイトによると

Netlify Dev automatically:

  • Detects and runs your site generator
  • Makes environment variables available
  • Performs edge logic and routing rules
  • Compiles and runs cloud functions

とのことなので、ほとんどのプロジェクトでは特に何も考えずに netlify dev するだけでいい感じでビルド&開発用サーバー起動をしてくれるっぽいです。

一応、

$ netlify dev -c "yarn start"

のように -c で実行コマンドを明示することもできます。

netlify dev を実行すると、 localhost:8888 で開発用サーバーが起動して、

  • プロジェクト(静的サイト)が http://localhost:8888/ でホストされる
  • Functionsが http://localhost:8888/.netlify/functions/{function名} でホストされる

という状態になります👍

Node.jsでMailgunを使ってメール送信するfunctionの実装例

ではいよいよ、Mailgunを使ってメール送信するfunctionの具体的な実装例を示します。

MailgunのJavaScript用SDKはいくつか実装がありますが、今回は一番スター数の多い mailgun-js を使うことにします。

$ npm i -S mailgun-js
// functions/mailgun.js
const mailgun = require('mailgun-js')({ apiKey: 'key-xxx', domain: 'mg.xxx.xxx' });

exports.handler = async event => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' }
  }

  const req = JSON.parse(event.body);

  const message = {
    from: 'noreply <noreply@xxx.xxx>',
    to: req.email,
    subject: 'こんにちは',
    text: `${name}様 こんにちは`,
  };

  try {
    await mailgun.messages().send(message);
    return { statusCode: 200, body: 'sent' };
  } catch (e) {
    return { statusCode: 500, body: e.message || e };
  }
};

MailgunのAPIキーは以下のURLで確認できると思います。( Private API key のほうです)

https://app.mailgun.com/app/account/security/api_keys

netlify dev を実行して開発用サーバーが起動している状態で、動作を確認してみましょう。

$ curl http://localhost:8888/.netlify/functions/mailgun -X POST -H "Content-Type: application/json" -d '{"name":"テスト","email":"あなたのメールアドレス"}'

ターミナルに sent が出力され、

Subject: こんにちは
テスト様 こんにちは

というメールが受け取れると思います👍

APIキーを環境変数で受け取りたい場合

APIキーやドメイン名をハードコードせずに環境変数で受け取りたい場合もあると思います。

その場合は、例えば MAILGUN_API_KEY MAILGUN_DOMAIN といった環境変数をfunction実行環境に用意しておいた上で、以下のようにfunctionのコードを修正すればよいでしょう。

// functions/mailgun.js
const apiKey = process.env.MAILGUN_API_KEY;
const domain = process.env.MAILGUN_DOMAIN;
const mailgun = require('mailgun-js')({ apiKey: apiKey, domain: domain });

環境変数の設定方法ですが、Netlify Devでは .env が自動で読み込まれるようになっています。

参考

ので、以下のような内容でプロジェクトルートに .env ファイルを作っておけばOKです。

MAILGUN_API_KEY=key-xxx
MAILGUN_DOMAIN=mg.xxx.xxx

プロダクション環境でfunction実行環境に環境変数を渡すには、Netlifyの管理画面( https://app.netlify.com/sites/{サイト名}/settings/deploys#environment )で設定しておきます。

代わりに netlify.toml というNetlify用の設定ファイルをコードベースのルートに持つことで、Web UI上で設定するのと同じことがファイル経由でもできます。

ちなみに、Netlifyには管理画面に設定した各種デプロイ設定よりも優先して読み込まれる netlify.toml という設定ファイルの仕組みがあるのですが、 netlify.toml で設定した環境変数は ビルドコマンド実行時にしか読み込まれず、functionの実行環境には渡されない ので、注意が必要です。

分かりづらいですが、公式リファレンスに以下のとおり「Web UIで設定した環境変数はサーバーレスfunctionのランタイムからも利用可能」という言及があり、逆に言えば「tomlで設定した環境変数はビルドタイムにしか使えない」という意味に取れます。

Environment variables set in the UI are also accessible in other environments associated with your site, including serverless functions at runtime, snippet injection during post processing, and more.

Build environment variables

この件についてのこれ以上の公式情報は見つけることができませんでしたが、こちらのスレッド でも「できない」で結論しているようですし、僕自身が実際に動かして確認してみたので、間違いではないと思います。

Nuxt.js + axiosの場合の注意点

最後に、Nuxt.js + axiosでFunctionsのエンドポイントをフロントエンドから利用したい場合の注意点に言及しておきます。

以下の公式リファレンスにも書かれているとおり、axiosでは、特に設定しなければデフォルトでベースURLが http://localhost:3000/ になってしまいます。

https://axios.nuxtjs.org/options

なので、 netlify dev して http://localhost:8888 でサイトを動かしている状態で、axiosで /.netlify/functions/{function名} にリクエストしようとすると、 http://localhost:3000/.netlify/functions/{function名} にリクエストしてしまい、CORS違反でエラーになります。

同様に、プロダクション環境にデプロイしたあとも、 https://{サイト名}.netlify.com/.netlify/functions/{function名} ではなく http://localhost:3000/.netlify/functions/{function名} にリクエストしようとしてしまいCORS違反でエラーになります。

これを解決するには、axiosの設定でベースURLを明示しておくことが必要です。

Nuxt.jsの場合、 nuxt.config.js

axios: {
},

の箇所に設定したいaxiosのオプションを書けばOKです。

ググると

axios: {
    baseURL: process.env.NODE_ENV === "production" ? "https://{本番のホスト名}/" : "http://localhost:3000/"
},

のような解法が散見されますが、もっとシンプルに

axios: {
    baseURL: '/'
},

で大丈夫です👌

Nuxt.jsとNetlify Functionsを併用するサンプルとしては こちらのリポジトリ が参考になります。

Mailgunの実装例もあります。

まとめ

  • Netlify Functionsを使えば、Netlifyでホストしているサイトに簡単にサーバーレスfunctionを導入できる!
  • しかも無料枠が結構たっぷりでありがたい!
  • Mailgunを使ったメール送信処理も簡単に実装できちゃう!
  • function内で環境変数を使いたい場合、 netlify.toml で設定した環境変数は使えないので要注意!
  • Nuxt.js + axiosの場合はベースURLを明示的に設定しておく必要があるので要注意!
GitHubで編集を提案

Discussion