Skip to content

Instantly share code, notes, and snippets.

@BlackPrincess
Last active December 30, 2015 23:59
Show Gist options
  • Save BlackPrincess/7904740 to your computer and use it in GitHub Desktop.
Save BlackPrincess/7904740 to your computer and use it in GitHub Desktop.
documentメモ

gmailを使ってメールを送信する

gmailのsmtpサーバー使うのが多分いちばん楽に用意できると思うのでgmailのsmtpサーバー使って送信する例を書きます。

trait GmailConfig extends SkinnyMailerConfig {
  override def mailSmtpHost = "smtp.gmail.com"
  /*
  アカウント設定
   */
  override def mailUser = "" // あなたのgmailアカウント名
  override def mailPassword = "password" // あなたのgmailパスワード
  override def mailDefaultFrom = "[email protected]" // デフォルトの送信元。書かなくてもOKですが、[email protected]が送信アドレスになるはずです。
}

SkinnyMailerConfigはデフォルトではsmtpsを使用して送信するせていになっているため、gmailサーバーならこれでOKなはずです


Mailerの実装

SkinnyMailerとSkinnyMailerConfigのmixinします。

class MyMailer extends SkinnyMailer with GmailConfig {
  def send_email = mail(to = "送信先@gmail.com",
    subject = "タイトル",
    text = "コンテンツ")
    .deliver
}

deliverメソッドを呼び出すとメールが送信されます。

mailメソッドは多分いちばんよく使われるテキストメール用に

def mail(to: String, from: String = config.mailDefaultFrom, subject: String = "", text: String = "") = getDefaultMessage
    .from(new InternetAddress(from))
    .to(to)
    .subject(subject, mailDefaultCharset)
    .text(text, mailDefaultCharset)

と定義されているだけなので、下記のように書いてもOKです。

class MyMailer extends SkinnyMailer with GmailConfig {
  def send_email = getDefaultMessage
    .to("送信先@gmail.com")
    .from("送信元@gmail.com")
    .subject("タイトル")
    .text("コンテンツ")
    .deliver
}

添付ファイルを送りたい

class MyMailer extends SkinnyMailer with GmailConfig {
  def send_email = mail(to = "送信先@gmail.com", subject = "タイトル", text = "コンテンツ")
    .attachment("ファイル名", "ファイルパス")
    .attachment("ファイル名", "ファイルパス")
    .deliver
}

これももちろん、下記のように書いてもOKです。

class MyMailer extends SkinnyMailer with GmailConfig {
  def send_email = getDefaultMessage
    .to("送信先@gmail.com")
    .from("送信元@gmail.com")
    .subject("タイトル")
    .text("コンテンツ")
    .attachment("ファイル名", "ファイルパス")
    .attachment("ファイル名", "ファイルパス")
    .deliver
}

メールテンプレートを使いたい

sspでも使えばいいんじゃないかな!
ssp使えないならStringInterporationとか使えばいいんじゃないかな!

メールのテストをしたい。

テスト対象クラスにSkinnyMockMailerをmixinします。

object TestMailer extends MyMailer extends SkinnyMockMailer

テストクラスの方にSkinnyMessageHelperをmixinします。

class FooSpec extends FlatSpec 
  with ShouldMatchers with SkinnyMessageHelper 

一通しか送信しないような場合は、SkinnyMailTestSupportを利用すると便利です。

package skinny.test

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import skinny.mail._
import javax.mail.MessagingException

class SkinnyMailTestSupportSpec extends FlatSpec 
  with ShouldMatchers with SkinnyMessageHelper 
  with SkinnyMailTestSupport {
  behavior of "SkinnyMailTestSupportSpec"

  override val singleMailTo = "[email protected]"

  object MyMailer extends SkinnyMailer 
    with SkinnyMailerConfig 
    with SkinnyMockMailer {
    def message = {
      mail(to = singleMailTo,
        subject = "test subject 日本語",
        text="body 日本語").deliver
    }
  }

  it should "singleMail basic" in {
    singleMail()(MyMailer.message) { msg =>
      msg.getSubject should be("test subject 日本語")
      msg.getMailText should be("body 日本語")
      // 添付ファイルを取りたいなら
      // msg.getMailAttachmentFiles
    } should be(true)
  }
}

下記が書き方の定型文です。

  singleMail(/* 送信されるメールアドレス。デフォルトはsingleMailToなのでto = singleMailToと書いた場合はいらない */)(/* ここで送信するメソッドを与える */) { msg =>
    // ここでアサーション処理
  } should be(true) // 1通のメールが送信されてきたときのみtrueを返す

これが定型文です。

複数同一アドレスに送信するようなテストを書きたい

SkinnyMailerはjavax.mailをラップして作っただけなので、mock_javamailを使えばできます。

it should "be available" in {
  Mailbox.clearAll()
  TestMailer.message.isRight should be(true)
  TestMailer.message.isRight should be(true)
  val inbox = Mailbox.get("[email protected]")
  // 一通目
  inbox.get(0).getSubject should be("subject")
  inbox.get(0).getMailText should be("body")

  // 二通目
  inbox.get(1).getSubject should be("subject")
  inbox.get(1).getMailText should be("body")
}

ちなみにConfigを毎回mixinするのはたるいので mail.confを用意して

mail {
  default {
    debug=true
    mimeVersion="1.0"
    charset="UTF-8"
    contentType="text/plain"
    from="[email protected]"
    smtp {
      host="smtp.skinny.org"
      port=587
      connectionTimeout=6000
      timeout=6000
      auth=false
      starttls {
        enable:true
      }
    }
    transportProtocol="smtp"
    user=""
    password=""
  }
}

こんな設定を書いておいて

class FooMailer extends SkinnyDefaultMailer

とするだけで終わりです。
でも、この設定ファイルは非常に微妙で、手抜き感に溢れてます。あとでやります。

TODO:

  • deliverをEitherで返すのは微妙?
  • 設定ファイルの設計…
  • 設定ファイルがproductionやdevelopで分けれないって…
  • リファクタリング
  • CastExceptionを潰す
  • あと何をやればいいかわからなくなってきた

https://github.com/BlackPrincess/skinny-framework/tree/mail

@seratch
Copy link

seratch commented Dec 11, 2013

ありがとうございます。なかなか迷いますよねぇ。

  • deliverをEitherで返すのは微妙?

例外でいいんじゃないかなって思います。Skinny 全体としても強い理由がなければ Either より例外を優先してます。Scala on Rails ですし。

  • 設定ファイルの設計…
  • 設定ファイルがproductionやdevelopで分けれないって…

SkinnyDefaultMailer 的な感じがいいですね。デフォルトは mail.conf ではなく application.conf から渡すようにしたいです。実装は共通化できてないんですけど ORM の dbmigration が env も含めて対応していて、同じようにやれればと思います。

  • SkinnyMailerConfig のキー名の mail プレフィックス

必要ですかね。なくてもいいんじゃないかと思いました。

  • 添付ファイル

ファイルパス以外のバリエーションもありそうですね。InputStream とか読み込んだ byte array を渡せるとか。私もまだ見通せてないですが。

  • テンプレートエンジン

やるなら Scalate をデフォルトでサポートするかなという感じですね。text("xxx") の代わりに render("/mailer/hoge", attributes) とか渡したら WEB-INF/views/mailer/hoge.txt.ssp やら hoge.html.ssp やらを使うとかそんなのかな。text or html の設定をどう渡すかってのもあるけど。

  • テストのサポート

ActionMailer のテストは割と簡単に出来ていいなぁと思うので、こだわりたいですね。SkinnyMailerTest みたいな trait を継承したら MailBox は隠蔽される、とかはあってもよいのかも。

  • getDefaultMessage とか getMessage とか

getDefaultMessage って public API としては違う名前にしたいですね。何も指定せずに mail を呼び出せればいいんじゃないかと(config の default が設定済みの Message が返る)。利用側に message とか mail とか似たような複数のキーワードを意識させたくない気がします。

  • getXXX

getXXX は基本的には避けたいですね。あと Java 側の getXXX を頑張ってラップして get なしにしたいですね。test の mock のやつもラップしないといけないけど。

で、今後、どうしましょうか?とりあえず気が済むところまでやってあとは私がいじってもいいということであれば区切りの良いところで pull request いただければ、無条件に merge して引き継ぎますが。。。その際は mailer というサブプロジェクトにしていただければと思います。

@BlackPrincess
Copy link
Author

レビューありがとうございます!

とりあえず指摘して頂いた点に関しては僕の方で全部対応して、pull requestしようかなと思います。

1ユーザーとしては、「どうするのがいいかなと悩んでいる」のissueを片付けてもらいたいと思う気持ちがあるので、「これサポートしなきゃな」というのがあればそういうのは引き続きやりたいですね。(自分でできる範囲のことであれば協力したいし、自分のできない範囲の進化は速ければ速いほどいいので)

@seratch
Copy link

seratch commented Dec 11, 2013

了解しました!よろしくお願いします。

1ユーザーとしては、「どうするのがいいかなと悩んでいる」のissueを片付けてもらいたいと思う気持ちがあるので、「これサポートしなきゃな」というのがあればそういうのは引き続きやりたいですね。(自分でできる範囲のことであれば協力したいし、自分のできない範囲の進化は速ければ速いほどいいので)

ぜひぜ。pull request を merge したタイミングで team に追加しますね。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment