├── .envrc
├── project
├── build.properties
├── plugins.sbt
└── ScalacOptions.scala
├── notes
├── about.markdown
├── 0.1.1.markdown
├── 0.1.3.markdown
├── 0.1.4.markdown
├── 0.1.2.markdown
└── 0.1.0.markdown
├── courier
└── src
│ ├── main
│ ├── scala-2.11
│ │ └── compat.scala
│ ├── scala-2.12
│ │ └── compat.scala
│ ├── scala-3
│ │ └── compat.scala
│ ├── scala-2.13
│ │ └── compat.scala
│ └── scala
│ │ ├── package.scala
│ │ ├── defaults.scala
│ │ ├── signer.scala
│ │ ├── envelope.scala
│ │ ├── mailer.scala
│ │ ├── content.scala
│ │ └── sessions.scala
│ └── test
│ └── scala
│ └── mailspec.scala
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── integration-tests
└── src
│ └── test
│ └── scala
│ └── mailspec.scala
├── LICENSE
└── README.md
/.envrc:
--------------------------------------------------------------------------------
1 | dotenv
2 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.5.8
2 |
--------------------------------------------------------------------------------
/notes/about.markdown:
--------------------------------------------------------------------------------
1 | [courier](https://github.com/softprops/courier#readme) delivers electronic mail in scala
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
2 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")
3 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
--------------------------------------------------------------------------------
/courier/src/main/scala-2.11/compat.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | object Compat {
4 | def asJava[T](set: Set[T]): java.util.Set[T] = {
5 | import collection.JavaConverters._
6 | set.asJava
7 | }
8 | }
--------------------------------------------------------------------------------
/courier/src/main/scala-2.12/compat.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | object Compat {
4 | def asJava[T](set: Set[T]): java.util.Set[T] = {
5 | import collection.JavaConverters._
6 | set.asJava
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/courier/src/main/scala-3/compat.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | object Compat {
4 | def asJava[T](set: Set[T]): java.util.Set[T] = {
5 | import scala.jdk.CollectionConverters._
6 | set.asJava
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/courier/src/main/scala-2.13/compat.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | object Compat {
4 | def asJava[T](set: Set[T]): java.util.Set[T] = {
5 | import scala.jdk.CollectionConverters._
6 | set.asJava
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/notes/0.1.1.markdown:
--------------------------------------------------------------------------------
1 | ## fixes
2 |
3 | * Actually set the `cc` and `bcc` of messages ( cause everyone's gotta know )
4 | * Not setting a default envelope body was a poor choice. A default empty body is now provided with all envelopes.
5 |
--------------------------------------------------------------------------------
/notes/0.1.3.markdown:
--------------------------------------------------------------------------------
1 | # fixes
2 |
3 | * [#8](https://github.com/softprops/courier/pull/8) SMTP headers were not being send [mariusmuja][https://github.com/mariusmuja]
4 |
5 | # enhancements
6 |
7 | * [#7](https://github.com/softprops/courier/pull/7) added documentation on testing [mcamou](https://github.com/mcamou)
8 |
9 | # misc
10 |
11 | * dropped scala 2.9.3 support, added scala 2.11 support
12 |
--------------------------------------------------------------------------------
/courier/src/main/scala/package.scala:
--------------------------------------------------------------------------------
1 | import javax.mail.internet.InternetAddress
2 |
3 | /** An agreeable email interface for scala. */
4 | package object courier {
5 |
6 | implicit class addr(name: String){
7 | def `@`(domain: String): InternetAddress = new InternetAddress(s"$name@$domain")
8 | def at = `@` _
9 | /** In case whole string is email address already */
10 | def addr = new InternetAddress(name)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/courier/src/main/scala/defaults.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import javax.mail.{Session => MailSession}
4 | import java.util.Properties
5 | import javax.mail.internet.MimeMessage
6 | import scala.concurrent.ExecutionContext
7 |
8 | object Defaults {
9 | val session = MailSession.getDefaultInstance(new Properties())
10 |
11 | val mimeMessageFactory: MailSession => MimeMessage = new MimeMessage(_)
12 |
13 | implicit val executionContext: ExecutionContext = ExecutionContext.Implicits.global
14 | }
15 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Scala CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Set up JDK 11
20 | uses: actions/setup-java@v3
21 | with:
22 | java-version: '11'
23 | distribution: 'temurin'
24 | cache: 'sbt'
25 | - name: Run tests
26 | run: sbt ci
27 |
--------------------------------------------------------------------------------
/notes/0.1.4.markdown:
--------------------------------------------------------------------------------
1 | # fixes
2 |
3 | * [#17](https://github.com/softprops/courier/pull/17) The reply-to address was not being sent - [laurencer](https://github.com/laurencer)
4 | * [#13](https://github.com/softprops/courier/pull/13) Allow to set charset for subject - [tzeman77](https://github.com/tzeman77)
5 |
6 | # enhancements
7 | * [#19](https://github.com/softprops/courier/pull/19) Add Scala 2.12 support - [rleibman](https://github.com/rleibman)
8 |
9 | # misc
10 | * [#18](https://github.com/softprops/courier/pull/18) Use implicit class [ChristopherDavenport](https://github.com/ChristopherDavenport)
11 |
--------------------------------------------------------------------------------
/notes/0.1.2.markdown:
--------------------------------------------------------------------------------
1 | ## additions
2 |
3 | * Includes a patch from [@rowlandwatkins](https://github.com/rowlandwatkins) for adding an interface for attachments as bytes.
4 |
5 | Below is an example
6 |
7 | mailer(Envelope.from("you" `@` "work.com")
8 | .to("boss" `@` "work.com")
9 | .subject("tps report")
10 | .content(Multipart()
11 | .attachBytes("ASCII TPS Template".getBytes(), "ascii-tps-template.txt", "text/plain")
12 | .html("
IT'S IMPORTANT
")))
13 | .onSuccess {
14 | case _ => println("delivered report")
15 | }
16 |
17 | Published for scala 2.9.3 _and_ 2.10
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *#
2 | *.iml
3 | *.ipr
4 | *.iws
5 | *.pyc
6 | *.tm.epoch
7 | *.vim
8 | */project/boot
9 | */project/build/target
10 | */project/project.target.config-classes
11 | *-shim.sbt
12 | *~
13 | .#*
14 | .*.swp
15 | .DS_Store
16 | .cache
17 | .cache
18 | .classpath
19 | .codefellow
20 | .ensime*
21 | .eprj
22 | .history
23 | .idea
24 | .manager
25 | .multi-jvm
26 | .project
27 | .scala_dependencies
28 | .scalastyle
29 | .settings
30 | .tags
31 | .tags_sorted_by_file
32 | .target
33 | .worksheet
34 | Makefile
35 | TAGS
36 | lib_managed
37 | logs
38 | project/boot/*
39 | project/plugins/project
40 | src_managed
41 | target
42 | tm*.lck
43 | tm*.log
44 | tm.out
45 | worker*.log
46 | /bin
47 | .creds.sh
48 | project/.gnupg/
49 | .bloop/
50 | .metals/
51 | .bsp/
52 | .env
53 |
--------------------------------------------------------------------------------
/notes/0.1.0.markdown:
--------------------------------------------------------------------------------
1 | ## initial release
2 |
3 | Courier is meant to simplify the ease in which you can use to tools built into the jdk to send electronic mail in scala using [Futures](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future).
4 |
5 | To send a message to your mom
6 |
7 | import courier._, Defaults._
8 | val mailer = Mailer("smtp.gmail.com", 587)
9 | .auth(true)
10 | .as("you@gmail.com", "p@$$w3rd")
11 | .startTtls(true)()
12 |
13 | mailer(Envelope.from("you" `@` "gmail.com")
14 | .to("mom" `@` "gmail.com")
15 | .subject("miss you")
16 | .content(Text("hi mom"))).onSuccess {
17 | case _ => println("message delivered")
18 | }
19 |
20 | For more information see the project's [readme](https://github.com/softprops/courier#readme)
21 |
--------------------------------------------------------------------------------
/integration-tests/src/test/scala/mailspec.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import scala.concurrent.Await
4 | import scala.concurrent.ExecutionContext.Implicits.global
5 | import scala.concurrent.duration._
6 |
7 |
8 | class MailSpec extends munit.FunSuite {
9 | // create a gmail app password https://myaccount.google.com/apppasswords
10 | test("the mailer should send an email") {
11 | assume(sys.env("CI").isEmpty(), "This test is meant to be ran locally.")
12 | val email = sys.env("IT_EMAIL")
13 | val password = sys.env("IT_PASSWORD")
14 | val mailer = Mailer("smtp.gmail.com", 587)
15 | .auth(true)
16 | .as(email, password)
17 | .startTls(true)()
18 | val mId = Await.result(mailer(Envelope.from(email.addr)
19 | .to(email.addr)
20 | .subject("miss you")
21 | .content(Text("hi mom"))), 10.seconds)
22 | assert(mId.nonEmpty, "Message-ID should be set by the Transport.send call")
23 | }
24 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Doug Tangren
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/courier/src/test/scala/mailspec.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import java.util.Properties
4 |
5 | import javax.mail.Provider
6 | import org.jvnet.mock_javamail.{Mailbox, MockTransport}
7 |
8 | import scala.concurrent.Await
9 | import scala.concurrent.ExecutionContext.Implicits.global
10 | import scala.concurrent.duration._
11 |
12 | class MockedSMTPProvider extends Provider(Provider.Type.TRANSPORT, "mocked", classOf[MockTransport].getName, "Mock", null)
13 |
14 | class MailSpec extends munit.FunSuite {
15 | private val mockedSession = javax.mail.Session.getDefaultInstance(new Properties() {{
16 | put("mail.transport.protocol.rfc822", "mocked")
17 | }})
18 | mockedSession.setProvider(new MockedSMTPProvider)
19 |
20 |
21 | test("the mailer should send an email") {
22 | val mailer = Mailer(mockedSession)
23 | val future = mailer(Envelope.from("someone@example.com".addr)
24 | .to("mom@gmail.com".addr)
25 | .cc("dad@gmail.com".addr)
26 | .subject("miss you")
27 | .content(Text("hi mom")))
28 |
29 | Await.ready(future, 5.seconds)
30 | val momsInbox = Mailbox.get("mom@gmail.com")
31 | assertEquals(momsInbox.size, 1)
32 | val momsMsg = momsInbox.get(0)
33 | assertEquals(momsMsg.getContent, "hi mom")
34 | assertEquals(momsMsg.getSubject, "miss you")
35 | }
36 | }
--------------------------------------------------------------------------------
/courier/src/main/scala/signer.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import java.security.PrivateKey
4 | import java.security.cert.X509Certificate
5 |
6 | import javax.mail.internet.{MimeBodyPart, MimeMultipart}
7 | import org.bouncycastle.cert.jcajce.JcaCertStore
8 | import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder
9 | import org.bouncycastle.mail.smime.SMIMESignedGenerator
10 |
11 | case class Signer(signingKey: PrivateKey, signingCert: X509Certificate, certStore: Set[X509Certificate]) {
12 | private val gen: SMIMESignedGenerator = new SMIMESignedGenerator()
13 | private val certs = new JcaCertStore(Compat.asJava(certStore + signingCert))
14 | gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().build("SHA256withRSA", signingKey, signingCert))
15 | gen.addCertificates(certs)
16 |
17 | def sign(content: Content): MimeMultipart = {
18 | val preppedContent = getContentToSign(content)
19 | gen.generate(preppedContent)
20 | }
21 |
22 | private def getContentToSign(c: Content): MimeBodyPart = {
23 | val bp = new MimeBodyPart()
24 | c match {
25 | case mp: Multipart => bp.setContent(mp.parts)
26 | case Text(txt, charset) => bp.setContent(txt, s"text/plain; charset=$charset")
27 | case _: Signed => throw new UnsupportedOperationException("Nested signed entities not supported")
28 | }
29 | bp
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/courier/src/main/scala/envelope.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import java.nio.charset.Charset
4 | import javax.mail.internet.InternetAddress
5 |
6 | object Envelope {
7 | def from(addr: InternetAddress) =
8 | Envelope(addr)
9 | }
10 |
11 | case class Envelope(
12 | from: InternetAddress,
13 | _subject: Option[(String, Option[Charset])] = None,
14 | _to: Seq[InternetAddress] = Seq.empty[InternetAddress],
15 | _cc: Seq[InternetAddress] = Seq.empty[InternetAddress],
16 | _bcc: Seq[InternetAddress] = Seq.empty[InternetAddress],
17 | _replyTo: Option[InternetAddress] = None,
18 | _replyToAll: Option[Boolean] = None,
19 | _headers: Seq[(String, String)] = Seq.empty[(String, String)],
20 | _content: Content = Text("")) {
21 |
22 | def subject(s: String) = copy(_subject = Some((s, None)))
23 | def subject(s: String, ch: Charset) = copy(_subject = Some((s, Some(ch))))
24 | def to(addrs: InternetAddress*) = copy(_to = _to ++ addrs)
25 | def cc(addrs: InternetAddress*) = copy(_cc = _cc ++ addrs)
26 | def bcc(addrs: InternetAddress*) = copy(_bcc = _bcc ++ addrs)
27 | def replyTo(addr: InternetAddress) = copy(_replyTo = Some(addr))
28 | def replyAll = copy(_replyToAll = Some(true))
29 | def headers(hdrs: (String, String)*) = copy(_headers = _headers ++ hdrs)
30 | def content(c: Content) = copy(_content = c)
31 |
32 | def contents = _content
33 | def subject = _subject
34 | @deprecated("use `to` instead", "0.1.1")
35 | def recipients = _to
36 | def to = _to
37 | def cc = _cc
38 | def bcc = _bcc
39 | def replyTo = _replyTo
40 | def headers = _headers
41 | }
42 |
--------------------------------------------------------------------------------
/courier/src/main/scala/mailer.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import javax.mail.internet.MimeMessage
4 | import javax.mail.{Message, Transport, Session => MailSession}
5 |
6 | import scala.concurrent.{ExecutionContext, Future}
7 |
8 | object Mailer {
9 | def apply(host: String, port: Int): Session.Builder =
10 | Mailer().session.host(host).port(port)
11 | }
12 |
13 | case class Mailer(_session: MailSession = Defaults.session,
14 | signer: Option[Signer] = None,
15 | mimeMessageFactory: MailSession => MimeMessage = Defaults.mimeMessageFactory) {
16 | def session = Session.Builder(this)
17 |
18 | def apply(e: Envelope)(implicit ec: ExecutionContext): Future[Option[String]] = {
19 | val msg = mimeMessageFactory(_session)
20 |
21 | e.subject.foreach {
22 | case (subject, Some(charset)) => msg.setSubject(subject, charset.name())
23 | case (subject, None) => msg.setSubject(subject)
24 | }
25 | msg.setFrom(e.from)
26 | e.to.foreach(msg.addRecipient(Message.RecipientType.TO, _))
27 | e.cc.foreach(msg.addRecipient(Message.RecipientType.CC, _))
28 | e.bcc.foreach(msg.addRecipient(Message.RecipientType.BCC, _))
29 | e.replyTo.foreach(a => msg.setReplyTo(Array(a)))
30 | e.headers.foreach(h => msg.addHeader(h._1, h._2))
31 | e.contents match {
32 | case Text(txt, charset) => msg.setText(txt, charset.displayName)
33 | case mp: Multipart => msg.setContent(mp.parts)
34 | case Signed(body) =>
35 | if (signer.isDefined) msg.setContent(signer.get.sign(body))
36 | else throw new IllegalArgumentException("No signer defined, cannot sign!")
37 | }
38 |
39 | Future {
40 | // sending the email sets the message id
41 | val _ = Transport.send(msg)
42 | Option(msg.getMessageID())
43 | }
44 | }
45 | }
46 |
47 |
48 |
--------------------------------------------------------------------------------
/courier/src/main/scala/content.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import java.io.File
4 | import java.nio.charset.Charset
5 |
6 | import javax.activation.{DataHandler, FileDataSource}
7 | import javax.mail.internet.{MimeBodyPart, MimeMultipart}
8 | import javax.mail.util.ByteArrayDataSource
9 |
10 | sealed trait Content
11 |
12 | case class Text(body: String, charset: Charset = Charset.defaultCharset) extends Content
13 |
14 | case class Signed(body: Content) extends Content
15 |
16 | case class Multipart(_parts: Seq[MimeBodyPart] = Seq.empty[MimeBodyPart], subtype: String = "mixed") extends Content {
17 | def add(part: MimeBodyPart): Multipart =
18 | this.copy(_parts = _parts :+ part)
19 |
20 | def add(
21 | bytes: Array[Byte],
22 | mimetype: String,
23 | name: Option[String] = None,
24 | disposition: Option[String] = None,
25 | description: Option[String] = None): Multipart =
26 | add(new MimeBodyPart {
27 | setContent(bytes, mimetype)
28 | disposition.foreach(setDisposition)
29 | description.foreach(setDescription)
30 | name.foreach(setFileName)
31 | })
32 |
33 | def text(str: String, charset: Charset = Charset.defaultCharset) =
34 | add(new MimeBodyPart {
35 | setText(str, charset.displayName())
36 | })
37 |
38 | def html(str: String, charset: Charset = Charset.defaultCharset) =
39 | add(new MimeBodyPart {
40 | setContent(str, s"text/html; charset=${charset.displayName()}")
41 | })
42 |
43 | def attach(file: File, name: Option[String] = None) =
44 | add(new MimeBodyPart {
45 | setDataHandler(new DataHandler(new FileDataSource(file)))
46 | setFileName(name.getOrElse(file.getName))
47 | })
48 |
49 | def attachBytes(bytes: Array[Byte], name: String, mimeType: String) =
50 | add(new MimeBodyPart {
51 | setDataHandler(new DataHandler(new ByteArrayDataSource(bytes, mimeType)))
52 | setFileName(name)
53 | })
54 |
55 | def parts =
56 | new MimeMultipart(subtype) {
57 | _parts.foreach(addBodyPart(_))
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/courier/src/main/scala/sessions.scala:
--------------------------------------------------------------------------------
1 | package courier
2 |
3 | import javax.mail.{ PasswordAuthentication, Session => MailSession }
4 | import java.util.Properties
5 |
6 | object Session {
7 | case class Builder(
8 | mailer: Mailer,
9 | _auth: Option[Boolean] = None,
10 | _startTls: Option[Boolean] = None,
11 | _ssl: Option[Boolean] = None,
12 | _trustAll: Option[Boolean] = None,
13 | _socketFactory: Option[String] = None,
14 | _host: Option[String] = None,
15 | _port: Option[Int] = None,
16 | _debug: Option[Boolean] = None,
17 | _creds: Option[(String, String)] = None,
18 | _signer: Option[Signer] = None) {
19 | def auth(a: Boolean) = copy(_auth= Some(a))
20 | def startTls(s: Boolean) = copy(_startTls = Some(s))
21 | def ssl(l: Boolean) = copy(_ssl = Some(l))
22 | def trustAll(t: Boolean) = copy(_trustAll = Some(t))
23 | def host(h: String) = copy(_host = Some(h))
24 | def port(p: Int) = copy(_port = Some(p))
25 | def debug(d: Boolean) = copy(_debug = Some(d))
26 | def as(user: String, pass: String) =
27 | copy(_creds = Some((user, pass)))
28 | def socketFactory(cls: String) = copy(_socketFactory = Some(cls))
29 | def sslSocketFactory = socketFactory("javax.net.ssl.SSLSocketFactory")
30 | def withSigner(s: Signer): Builder = copy(_signer=Some(s))
31 | def apply() =
32 | mailer.copy(_session = MailSession.getInstance(
33 | new Properties(System.getProperties) {
34 | _debug.map(d => put("mail.smtp.debug", d.toString))
35 | _auth.map(a => put("mail.smtp.auth", a.toString))
36 | // enable ESMTP
37 | _startTls.map(s => put("mail.smtp.starttls.enable", s.toString))
38 | _ssl.map(l => put("mail.smtp.ssl.enable", l.toString))
39 | _socketFactory.map(put("mail.smtp.socketFactory.class", _))
40 | _host.map(put("mail.smtp.host", _))
41 | _port.map(p => put("mail.smtp.port", p.toString))
42 | _trustAll.collect {
43 | case true => put("mail.smtp.ssl.trust", "*")
44 | }
45 | }, _creds.map {
46 | case (user, pass) =>
47 | new javax.mail.Authenticator {
48 | protected override def getPasswordAuthentication() =
49 | new PasswordAuthentication(user, pass)
50 | }
51 | }
52 | .orNull),
53 | signer=_signer)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/project/ScalacOptions.scala:
--------------------------------------------------------------------------------
1 | // https://tpolecat.github.io/2017/04/25/scalac-flags.html
2 | // Thanks Rob
3 | object ScalacOptions {
4 | val All = Seq(
5 | "-deprecation", // Emit warning and location for usages of deprecated APIs.
6 | "-encoding", "utf-8", // Specify character encoding used by source files.
7 | "-explaintypes", // Explain type errors in more detail.
8 | "-feature", // Emit warning and location for usages of features that should be imported explicitly.
9 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred
10 | "-language:experimental.macros", // Allow macro definition (besides implementation and application)
11 | "-language:higherKinds", // Allow higher-kinded types
12 | "-language:implicitConversions", // Allow definition of implicit functions called views
13 | "-unchecked", // Enable additional warnings where generated code depends on assumptions.
14 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
15 | "-Xfatal-warnings", // Fail the compilation if there are any warnings.
16 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
17 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
18 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit.
19 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
20 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
21 | "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`.
22 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
23 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit.
24 | "-Xlint:option-implicit", // Option.apply used implicit view.
25 | "-Xlint:package-object-classes", // Class or object defined in package object.
26 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
27 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
28 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
29 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
30 | "-Ywarn-dead-code", // Warn when dead code is identified.
31 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
32 | "-Ywarn-numeric-widen", // Warn when numerics are widened.
33 | "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
34 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced.
35 | "-Ywarn-unused:locals", // Warn if a local definition is unused.
36 | "-Ywarn-unused:params", // Warn if a value parameter is unused.
37 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
38 | "-Ywarn-unused:privates", // Warn if a private member is unused.
39 | "-Ywarn-value-discard" // Warn when non-Unit expression results are unused.
40 | )
41 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # courier
2 |
3 | [](https://maven-badges.herokuapp.com/maven-central/com.github.daddykotex/courier_2.13)
4 |
5 | deliver electronic mail with scala from the [future](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future)
6 |
7 | 
8 |
9 | ## install
10 |
11 | Via the copy and paste method
12 |
13 | ```scala
14 | libraryDependencies += "com.github.daddykotex" %% "courier" % "4.0.0-RC1"
15 | ```
16 |
17 | 3.2.0+ supports scala 2.11 to 3.1
18 |
19 | Note: Scala3 (or Dotty) is supported.
20 |
21 | - `3.0.0-RC1` for dotty: `0.27.0-RC1`
22 | - `3.0.0-M1` for dotty: `3.0.0-M1`
23 | - `3.0.0-M2` for dotty: `3.0.0-M2`
24 | - `3.0.1` scala 3: `3.0.1`
25 | - `3.1.9` scala 3: `3.1.0`
26 |
27 | ## usage
28 |
29 | deliver electronic mail via gmail
30 |
31 | ```scala
32 | import courier._, Defaults._
33 | import scala.util._
34 | val mailer = Mailer("smtp.gmail.com", 587)
35 | .auth(true)
36 | .as("you@gmail.com", "p@$$w3rd")
37 | .startTls(true)()
38 | mailer(Envelope.from("you" `@` "gmail.com")
39 | .to("mom" `@` "gmail.com")
40 | .cc("dad" `@` "gmail.com")
41 | .subject("miss you")
42 | .content(Text("hi mom"))).onComplete {
43 | case Success(_) => println("message delivered")
44 | case Failure(_) => println("delivery failed")
45 | }
46 |
47 | mailer(Envelope.from("you" `@` "work.com")
48 | .to("boss" `@` "work.com")
49 | .subject("tps report")
50 | .content(Multipart()
51 | .attach(new java.io.File("tps.xls"))
52 | .html("IT'S IMPORTANT
")))
53 | .onComplete {
54 | case Success(_) => println("delivered report")
55 | case Failure(_) => println("delivery failed")
56 | }
57 | ```
58 |
59 | If using SSL/TLS instead of STARTTLS, substitute `.startTls(true)` with `.ssl(true)` when setting up the `Mailer`.
60 |
61 | ### S/MIME
62 |
63 | Courier supports sending S/MIME signed email through its optional dependencies on the Bouncycastle cryptography libraries. It does not yet support sending encrypted email.
64 |
65 | Make sure the Mailer is instantiated with a signer, and then wrap your message in a Signed() object.
66 |
67 | ```scala
68 | import courier._
69 | import java.security.cert.X509Certificate
70 | import java.security.PrivateKey
71 |
72 | val certificate: X509Certificate = ???
73 | val privateKey: PrivateKey = ???
74 | val trustChain: Set[X509Certificate] = ???
75 |
76 | val signer = Signer(privateKey, certificate, trustChain)
77 | val mailer = Mailer("smtp.gmail.com", 587)
78 | .auth(true)
79 | .as("you@gmail.com", "p@$$w3rd")
80 | .withSigner(signer)
81 | .startTtls(true)()
82 | val envelope = Envelope
83 | .from("mr_pink" `@` "gmail.com")
84 | .to("mr_white" `@` "gmail.com")
85 | .subject("the jewelry store")
86 | .content(Signed(Text("For all I know, you're the rat.")))
87 |
88 | mailer(envelope)
89 | ```
90 |
91 | ## testing
92 |
93 | Since courier is based on JavaMail, you can use [Mock JavaMail](https://java.net/projects/mock-javamail) to execute your tests. Simply add the following to your `build.sbt`:
94 |
95 | ```scala
96 | libraryDependencies += "org.jvnet.mock-javamail" % "mock-javamail" % "1.9" % "test"
97 | ```
98 |
99 | With this library, you should, given a little bit of boilerplate, be able to set a test against a mocked Mailbox. This repo contains [an example](src/test/scala/mailspec.scala).
100 |
101 | ## changelog
102 |
103 | **4.0.0-RC1**
104 |
105 | - includes a breaking change in the Mailer#apply function. It now returns a `Future[String]` as opposed to a `Future[Unit]`.
106 |
107 | (C) Doug Tangren (softprops) and others, 2013-2018
108 |
--------------------------------------------------------------------------------