ZIO wrappers for messaging APIs, providing type-safe, functional interfaces for sending SMS messages through various providers.
- Type-safe: Uses
PhoneNumberfrom scala-phonenumber for validated phone numbers - Functional: Built on ZIO for composable, testable, and error-safe code
- Modular: Choose the integration you need (Twilio, Entrance Group, or implement your own)
- Cross-compiled: Supports both Scala 2.13 and Scala 3
The core module defines the SmsService trait that all implementations extend:
trait SmsService {
def sendMessage(to: Seq[PhoneNumber], message: String): Task[Unit]
}Also includes a no-op implementation that doesn't send messages (useful for testing, development, or disabling SMS):
SmsService.NoOp.layerTwilio SMS integration using the official Twilio SDK.
Entrance Group SMS integration with two implementation options:
- Hook Campaign: Use an existing campaign via webhook
- Manual Campaign: Create new campaigns per message
See zio-messaging-entrance-group/README.md for usage examples and details.
Add the following to your build.sbt:
libraryDependencies += "io.github.nafg.zio-messaging" %% "zio-messaging-twilio" % "<version>"For Bleep (bleep.yaml):
dependencies:
- io.github.nafg.zio-messaging::zio-messaging-twilio:<version>import io.github.nafg.messaging.SmsService
import io.github.nafg.messaging.twilio.{TwilioClient, TwilioSmsService}
import io.github.nafg.scalaphonenumber.PhoneNumber
import zio._
val twilioClientLayer = ZLayer.succeed(
TwilioClient.Config(
accountSid = "your-account-sid",
authToken = "your-auth-token"
)
) >>> TwilioClient.layer
val twilioSmsLayer = ZLayer.succeed(
TwilioSmsService.Config(
from = PhoneNumber.parseInternational("+15551234567").get
)
) ++ twilioClientLayer >>> TwilioSmsService.layer
val program = for {
sms <- ZIO.service[SmsService]
recipient = PhoneNumber.parseInternational("+15559876543").get
_ <- sms.sendMessage(Seq(recipient), "Hello from ZIO!")
} yield ()
program.provide(twilioSmsLayer)When you don't want to send actual messages (e.g., testing, local development, or feature toggling), use the no-op implementation:
import io.github.nafg.messaging.SmsService
val program = for {
sms <- ZIO.service[SmsService]
_ <- sms.sendMessage(recipients, "This won't actually send")
} yield ()
program.provide(SmsService.NoOp.layer)All phone numbers use the PhoneNumber type from scala-phonenumber:
import io.github.nafg.scalaphonenumber.PhoneNumber
// Parse international format
val number = PhoneNumber.parseInternational("+15551234567")
// Format as E.164
number.map(_.formatE164) // "+15551234567"Licensed under the Apache License, Version 2.0. See LICENSE for details.
Contributions are welcome! Please feel free to submit a Pull Request.