public
Last active

EmailService.scala improved with Exception Handling and Routes

  • Download Gist
EmailService.scala
Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
/*
* Copyright (C) 2012 47 Degrees, LLC
* http://47deg.com
* hello@47deg.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
 
package services
 
import akka.actor._
import play.api.libs.concurrent.Akka
import play.api.Play.current
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._
import akka.routing.SmallestMailboxRouter
import org.apache.commons.mail.{HtmlEmail, DefaultAuthenticator, EmailException}
import akka.actor.ActorDSL._
 
 
/**
* Smtp config
* @param tls if tls should be used with the smtp connections
* @param ssl if ssl should be used with the smtp connections
* @param port the smtp port
* @param host the smtp host name
* @param user the smtp user
* @param password thw smtp password
*/
case class SmtpConfig(tls : Boolean = false,
ssl : Boolean = false,
port : Int = 25,
host : String,
user : String,
password: String)
 
/**
* The email message sent to Actors in charge of delivering email
*
* @param subject the email subject
* @param recipient the recipient
* @param from the sender
* @param text alternative simple text
* @param html html body
*/
case class EmailMessage(
subject: String,
recipient: String,
from: String,
text: String,
html: String,
smtpConfig : SmtpConfig,
retryOn: FiniteDuration,
var deliveryAttempts: Int = 0)
 
/**
* Email service
*/
object EmailService {
 
/**
* Uses the smallest inbox strategy to keep 20 instances alive ready to send out email
* @see SmallestMailboxRouter
*/
val emailServiceActor = Akka.system.actorOf(
Props[EmailServiceActor].withRouter(
SmallestMailboxRouter(nrOfInstances = 50)
), name = "emailService"
)
 
 
/**
* public interface to send out emails that dispatch the message to the listening actors
* @param emailMessage the email message
*/
def send(emailMessage: EmailMessage) {
emailServiceActor ! emailMessage
}
 
/**
* Private helper invoked by the actors that sends the email
* @param emailMessage the email message
*/
private def sendEmailSync(emailMessage: EmailMessage) {
 
// Create the email message
val email = new HtmlEmail()
email.setTLS(emailMessage.smtpConfig.tls)
email.setSSL(emailMessage.smtpConfig.ssl)
email.setSmtpPort(emailMessage.smtpConfig.port)
email.setHostName(emailMessage.smtpConfig.host)
email.setAuthenticator(new DefaultAuthenticator(
emailMessage.smtpConfig.user,
emailMessage.smtpConfig.password
))
email.setHtmlMsg(emailMessage.html)
.setTextMsg(emailMessage.text)
.addTo(emailMessage.recipient)
.setFrom(emailMessage.from)
.setSubject(emailMessage.subject)
.send()
}
 
/**
* An Email sender actor that sends out email messages
* Retries delivery up to 10 times every 5 minutes as long as it receives
* an EmailException, gives up at any other type of exception
*/
class EmailServiceActor extends Actor with ActorLogging {
 
/**
* The actor supervisor strategy attempts to send email up to 10 times if there is a EmailException
*/
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 10) {
case emailException: EmailException => {
log.debug("Restarting after receiving EmailException : {}", emailException.getMessage)
Restart
}
case unknownException: Exception => {
log.debug("Giving up. Can you recover from this? : {}", unknownException)
Stop
}
case unknownCase: Any => {
log.debug("Giving up on unexpected case : {}", unknownCase)
Stop
}
}
 
/**
* Forwards messages to child workers
*/
def receive = {
case message: Any => context.actorOf(Props[EmailServiceWorker]) ! message
}
 
}
 
/**
* Email worker that delivers the message
*/
class EmailServiceWorker extends Actor with ActorLogging {
 
/**
* The email message in scope
*/
private var emailMessage: Option[EmailMessage] = None
 
/**
* Delivers a message
*/
def receive = {
case email: EmailMessage => {
emailMessage = Option(email)
email.deliveryAttempts = email.deliveryAttempts + 1
log.debug("Atempting to deliver message")
sendEmailSync(email)
log.debug("Message delivered")
}
case unexpectedMessage: Any => {
log.debug("Received unexepected message : {}", unexpectedMessage)
throw new Exception("can't handle %s".format(unexpectedMessage))
}
}
 
/**
* If this child has been restarted due to an exception attempt redelivery
* based on the message configured delay
*/
override def preRestart(reason: Throwable, message: Option[Any]) {
if (emailMessage.isDefined) {
log.debug("Scheduling email message to be sent after attempts: {}", emailMessage.get)
import context.dispatcher
// Use this Actors' Dispatcher as ExecutionContext
 
context.system.scheduler.scheduleOnce(emailMessage.get.retryOn, self, emailMessage.get)
}
}
 
override def postStop() {
if (emailMessage.isDefined) {
log.debug("Stopped child email worker after attempts {}, {}", emailMessage.get.deliveryAttempts, self)
}
}
 
}
 
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.