Heroku+SendGridとfreenomで空メール送信を受け付ける何か(使い捨て)をつくる

空メール送信をしたら会員登録URLが送られてきて、それをタップするとアプリが開いて、Facebookで会員登録! みたいな流れを作りたかった。

実装にあたり、ネックとなるのは、空メールを受け付けるところ。受信可能なメールアドレスをなんとかしてお金かけずに作りたい。

ということで、なんとなくやってみた。

ドメインを無料でとる

freenom というところで12ヶ月までなら無料で使えるドメインを取ることができる。 (無料とはいえ、ドメインを持つ以上は住所とかいろいろ入れないといけないので、そのあたりは自己責任で...)

今回は、とりあえず kodama.tk という適当なドメインをとった。ネーミングに特に理由はない。(なんとなくechoを日本語にしただけ)

とりあえずHerokuで適当なプロジェクトをつくる

アプリ名は kodamatk にして、

bundle exec rails new . --api --skip-bundle --skip-test --database=postgresql

したのをGitHubに置いて、pipeline設定すると

https://kodamatk.herokuapp.com/

が使えるようになる。

f:id:YusukeIwaki:20171004111006p:plain

ただ、何もコンテンツがないRailsアプリは404しか返してくれず、ちゃんと動いてるか判別がつきにくいので、

github.com

こんな感じで https://kodamatk.herokuapp.com/inspect/ip というエンドポイントをなんとなく作った。

Herokuアプリにカスタムドメインを設定する

devcenter.heroku.com

に従う。

$ heroku domains:add www.kodama.tk
Adding www.kodama.tk to ⬢ kodamatk... done
 ▸    Configure your app's DNS provider to point to the DNS Target www.kodama.tk.herokudns.com.
 ▸    For help, see https://devcenter.heroku.com/articles/custom-domains

The domain www.kodama.tk has been enqueued for addition
 ▸    Run heroku domains:wait 'www.kodama.tk' to wait for completion
$ heroku domains
=== kodamatk Heroku Domain
kodamatk.herokuapp.com

=== kodamatk Custom Domains
Domain Name    DNS Record Type  DNS Target
─────────────  ───────────────  ───────────────────────────
www.kodama.tk  CNAME            www.kodama.tk.herokudns.com

この最後の1行がポイント。 www.kodama.tk CNAME www.kodama.tk.herokudns.comドメイン設定側(今回の場合はfreenom)で指定する必要がある。

f:id:YusukeIwaki:20171004111708p:plain

これだけ!

めっちゃかんたん。

暫く待つと、 http://www.kodama.tk/inspect/ip でさっきのIPアドレス表示ページにアクセスできるようになる。

SendGridにカスタムドメインを設定する

こっちはやや面倒。2つやることがある。

Whitelabelの設定

f:id:YusukeIwaki:20171004112049p:plain

ここにある。

[Add whitelabel]をおすと、入力フォームがでてくるので

f:id:YusukeIwaki:20171004112206p:plain

こんなかんじで。subdomainのところはなんでもいいらしい。とりあえず適当に mail にした。

Saveすると

f:id:YusukeIwaki:20171004112442p:plain

こんなかんじで、CNAMEを3つ設定してくれ、っぽいことが出てくるので、あらためてドメイン設定側(今回の場合はfreenom)で指定する。

f:id:YusukeIwaki:20171004112703p:plain

しばらく待ってから、SendGrid側で「Validate」をすると、 ✘ だったのが ✔ になる。

これで、カスタムドメインでのメールが使えるようになる。

Inbound Parseを設定する

先の手順まででカスタムドメインでメールの送信はできるが、受信にはもう一手間必要。

SendGridには、Inbound Parseという、受信したメールを特定のURLにWebHookで通知してくれる超便利機能があるので、それを使う。

設定手順はわりとかんたん。 (詳しくは→ Inbound Email Parse Webhook - ドキュメント | SendGrid )

まずは、ドメイン設定側(今回の場合はfreenom)でMXレコードを追加する。

f:id:YusukeIwaki:20171004113302p:plain

これで、 xxxxxxxx@kodama.tk というメールアドレスにメールを送ると、SendGridの方に届くようになる。

次に、SendGridでWebhookの設定

f:id:YusukeIwaki:20171004113507p:plain

ここから、

f:id:YusukeIwaki:20171004113453p:plain

こんな感じで、適当にWebHookのURLを指定する。( www.kodama.tk は証明書を取っていないのでhttps通信では使えないので、 kodamatk.herokuapp.com のほうのURLを指定している)

あとは、Railsアプリ側でWebHookをさばく処理を書くだけ!

namespace :webhook do
  post :email, to: 'email#incoming'
end
class Webhook::EmailController < ::ApplicationController
  def incoming
    mail_from = params[:from]  # 空メールを送った人のアドレス
    mail_to = params[:to] # xxxx@kodama.tk
    mail_title = params[:subject]
    mail_body = params[:text]

    # 空メールの内容が正しければ、会員登録URLをmail_fromに送る
    handle_mail(mail_from, mail_to, mail_title, mail_body)

    render plain: "ok"
  end
end

というかんじで、使い捨てと割り切れば無料でおためしができた。

Galaxy Note 8 は割と使いにくい形をしている

先週くらいに、念願だったGalaxyNote 8を買った。 1ヶ月前くらいから予約してまで買ったのはこれが初めてだ。 ただ、一週間程度しか使ってなくても、少し後悔し始めている。

まえおき(蛇足)

私はわりとGalaxy Noteのファンのようで、

をこれまで買って使ってきた。

買ってないやつにも理由はあって、 Note4, Note5は動作がモッサリしてたので買ってない。 Note7は買おうとしていたら販売停止になってしまったw

という感じで、だいぶGalaxy Noteを買ってる方だとは思う。

Note 7を買えなかったときから、Note 8はどんな形であろうと買おうと決めていた。 ただファンだったから…。

いざ使ってみると…

f:id:YusukeIwaki:20170930081721j:plain

こんなにペンの使いづらいGalaxy Noteは初めてかも、と感じた。

デザイン重視なのか、ベゼルレスなのがものすごく使いにくいのである。 ペンを使う以上は両手で使うことになるのだけど、左手で持つ時に確実にディスプレイのエリアを持つことになる。また、右手でメモをとるにも宙に浮かせて文字を書くとか無理なので、やっぱり画面に手を置くことになる。 そうなると、この大画面にもかかわらず、まともにメモを取れるエリアが恐ろしく少なくなるのである。これは困る。

あと、端末が全体的にとても滑りやすい加工をされていて、電車の中で揺れたら落としてしまいそうになる。これも困る。

ということで、今のところSペンをまともに使いこなせていない。こんなGalaxy Noteは初めてである。

SペンのないGalaxy S8+で十分だったのではないかと後悔している。

「ちょうどいいサイズ感」は一体どこへ…?

Note 8、開封してさわったときから、実は第一印象があまりよくなかった。

重い & デカい

f:id:YusukeIwaki:20170930082737j:plain

Xperia Z ultraに比べると一回り小さくて軽い。のだけど、左手で持って右手で使うように設計されているZ ultraとは違って、GalaxyNote8はあくまで片手操作の設計だ。

3本の指で支えて親指で操作するには中途半端にでかくて重い。 サイズ感でいうとGalaxy Note 3~5くらいのほうが圧倒的に良かったと思う。

ということで

これから買う方は、 爆発しなくなったGalaxy Note 7 あたりも視野に入れてみてはどうでしょうか。

Railsのxxx_pathとかxxx_urlの引数を調べた

リファレンス読んでもいまいちわからなかったので、そういうときはRailsそのもののソースを読むに限る。

_pathgrepしてみると、

# actionpack/lib/action_dispatch/routing/route_set.rb

  def add(name, route)
    key       = name.to_sym
    path_name = :"#{name}_path"
    url_name  = :"#{name}_url"

    if routes.key? key
      @path_helpers_module.send :undef_method, path_name
      @url_helpers_module.send  :undef_method, url_name
    end
    routes[key] = route
    define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
    define_url_helper @url_helpers_module,  route, url_name,  route.defaults, name, UNKNOWN

    @path_helpers << path_name
    @url_helpers << url_name
  end

bundle exec rake routes したときに出てくるルート情報っぽいものを作ってる場所なんだけど、そこで define_url_helper ってのがあった。きっとこれだ。

# actionpack/lib/action_dispatch/routing/route_set.rb

  # Create a URL helper allowing ordered parameters to be associated
  # with corresponding dynamic segments, so you can do:
  #
  #   foo_url(bar, baz, bang)
  #
  # Instead of:
  #
  #   foo_url(bar: bar, baz: baz, bang: bang)
  #
  # Also allow options hash, so you can do:
  #
  #   foo_url(bar, baz, bang, sort_by: 'baz')
  #
  def define_url_helper(mod, route, name, opts, route_key, url_strategy)
    helper = UrlHelper.create(route, opts, route_key, url_strategy)
    mod.module_eval do
      define_method(name) do |*args|
        last = args.last
        options = \
          case last
          when Hash
            args.pop
          when ActionController::Parameters
            args.pop.to_h
          end
        helper.call self, args, options
      end
    end
  end

ここでさらに UrlHelper というのが出てくる。

# actionpack/lib/action_dispatch/routing/route_set.rb

  def initialize(route, options, route_name, url_strategy)
    @options      = options
    @segment_keys = route.segment_keys.uniq
    @route        = route
    @url_strategy = url_strategy
    @route_name   = route_name
  end

  def call(t, args, inner_options)
    controller_options = t.url_options
    options = controller_options.merge @options
    hash = handle_positional_args(controller_options,
                                  inner_options || {},
                                  args,
                                  options,
                                  @segment_keys)

    t._routes.url_for(hash, route_name, url_strategy)
  end

handle_positional_args… (そろそろメタっぽいコードを見るのが辛くなってきた)

# actionpack/lib/action_dispatch/routing/route_set.rb

  def handle_positional_args(controller_options, inner_options, args, result, path_params)
    if args.size > 0
      # take format into account
      if path_params.include?(:format)
        path_params_size = path_params.size - 1
      else
        path_params_size = path_params.size
      end

      if args.size < path_params_size
        path_params -= controller_options.keys
        path_params -= result.keys
      end
      inner_options.each_key do |key|
        path_params.delete(key)
      end

      args.each_with_index do |arg, index|
        param = path_params[index]
        result[param] = arg if param
      end
    end

    result.merge!(inner_options)
  end

本気で困ったときにもう一回見直すことにしよう・・。