Kotlin SDK (server-side)
Every call in this guide authenticates with your secret API key
(zy_live_* / zy_test_*). A secret key embedded in an APK is extractable by
anyone who downloads it — including one passed through BuildConfig, which
still lands in the compiled artifact. It can send on your account, read your
subscribers, and manage your configuration.
Use this SDK from a JVM backend, or from the server your Android app talks to. Your app calls your backend; your backend calls Zyphr. The snippets below assume that shape.
For the end-user auth flows that are safe to call from an app, use the
client-safe ZyphrClient with a publishable
application key (za_pub_*) instead. It ships in @zyphr-dev/node-sdk and
covers browser and React Native today — there is no publishable-key Kotlin
surface, so a native Android app needs a backend proxy.
The official Zyphr Kotlin SDK for JVM backends, built with OkHttp4 and Kotlin coroutines. Auto-generated from the OpenAPI specification.
Installation
Gradle (Kotlin DSL)
dependencies {
implementation("dev.zyphr:zyphr-sdk:0.1.0")
}
Gradle (Groovy)
implementation 'dev.zyphr:zyphr-sdk:0.1.0'
Configuration
Read the key from the environment on your server. Never inline it in source, and
never route it through BuildConfig — that ships it in the APK.
import dev.zyphr.sdk.infrastructure.ApiClient
val client = ApiClient(
apiKey = System.getenv("ZYPHR_API_KEY"),
basePath = "https://api.zyphr.dev/v1" // Optional custom endpoint
)
API Classes
The SDK exposes one API class per resource:
| Class | Description |
|---|---|
EmailsApi | Send and manage email messages |
PushApi | Send push notifications |
SmsApi | Send SMS text messages |
InboxApi | In-app notification inbox |
SubscribersApi | Manage subscriber profiles |
DevicesApi | Register push notification devices |
TemplatesApi | Create and manage templates |
TopicsApi | Pub/sub topics |
WebhooksApi | Configure webhook endpoints |
All API methods are suspend functions and must be called from a coroutine scope.
Emails
Send Email
import dev.zyphr.sdk.api.EmailsApi
import dev.zyphr.sdk.models.SendEmailRequest
val emails = EmailsApi(client)
val result = emails.sendEmail(
SendEmailRequest(
to = "user@example.com",
from = "hello@yourapp.com",
subject = "Welcome!",
html = "<h1>Hello!</h1><p>Thanks for signing up.</p>",
text = "Hello! Thanks for signing up.",
replyTo = "support@yourapp.com",
tags = listOf("welcome", "onboarding"),
metadata = mapOf("userId" to "user_123")
)
)
println("Email sent: ${result.id}")
Send with Template
val result = emails.sendEmail(
SendEmailRequest(
to = "user@example.com",
templateId = "welcome-email",
templateData = mapOf(
"name" to "John",
"actionUrl" to "https://yourapp.com/activate"
)
)
)
Get Email Status
val email = emails.getEmail(id = "msg_abc123")
println("Status: ${email.status}")
List Emails
val response = emails.listEmails(page = 1, perPage = 25)
for (email in response.data) {
println("${email.id}: ${email.status}")
}
Push Notifications
Send Push
import dev.zyphr.sdk.api.PushApi
import dev.zyphr.sdk.models.SendPushRequest
val push = PushApi(client)
push.sendPush(
SendPushRequest(
userId = "user_123",
title = "New Message",
body = "You have a new message",
data = mapOf("messageId" to "msg_456")
)
)
Send to Specific Device
push.sendPush(
SendPushRequest(
deviceId = "device_abc",
title = "New Message",
body = "You have a new message"
)
)
Silent Push
push.sendPush(
SendPushRequest(
userId = "user_123",
contentAvailable = true,
data = mapOf("type" to "sync", "resource" to "messages")
)
)
Rich Notifications
push.sendPush(
SendPushRequest(
userId = "user_123",
title = "Photo shared",
body = "Jane shared a photo with you",
imageUrl = "https://yourapp.com/photo.jpg",
badge = 3,
sound = "default",
actionButtons = listOf(
ActionButton(id = "view", title = "View"),
ActionButton(id = "dismiss", title = "Dismiss")
)
)
)
SMS
import dev.zyphr.sdk.api.SmsApi
import dev.zyphr.sdk.models.SendSmsRequest
val sms = SmsApi(client)
val result = sms.sendSms(
SendSmsRequest(
to = "+14155551234",
body = "Your verification code is 123456"
)
)
println("SMS sent: ${result.id}")
In-App Inbox
import dev.zyphr.sdk.api.InboxApi
import dev.zyphr.sdk.models.SendInboxRequest
val inbox = InboxApi(client)
inbox.sendInboxNotification(
SendInboxRequest(
subscriberId = "user_123",
title = "New Comment",
body = "John commented on your post",
actionUrl = "/posts/123#comments"
)
)
Subscribers
Create Subscriber
import dev.zyphr.sdk.api.SubscribersApi
import dev.zyphr.sdk.models.CreateSubscriberRequest
val subscribers = SubscribersApi(client)
val subscriber = subscribers.createSubscriber(
CreateSubscriberRequest(
externalId = "user_123",
email = "user@example.com",
phone = "+14155551234",
name = "John Doe",
metadata = mapOf("plan" to "pro")
)
)
Get Subscriber
val subscriber = subscribers.getSubscriber(id = "user_123")
println("Name: ${subscriber.name}")
Update Subscriber
subscribers.updateSubscriber(
id = "user_123",
UpdateSubscriberRequest(
name = "Jane Doe",
metadata = mapOf("plan" to "enterprise")
)
)
Delete Subscriber
subscribers.deleteSubscriber(id = "user_123")
Device Management
Register Device
The device API authenticates with your secret key, which must never ship in
an APK. Post the FCM token to your own backend and register from there, deriving
userId from the authenticated session. See
Device Management and the
Android push guide.
The snippets below show the request shape for reference — run them from your server, not the app.
import dev.zyphr.sdk.api.DevicesApi
import dev.zyphr.sdk.models.RegisterDeviceRequest
val devices = DevicesApi(client)
devices.registerDevice(
RegisterDeviceRequest(
userId = "user_123",
platform = RegisterDeviceRequest.Platform.android,
token = fcmToken,
metadata = mapOf("appVersion" to appVersion) // forwarded from the app
)
)
List Devices
val response = devices.listDevices(userId = "user_123")
for (device in response.data) {
println("${device.platform}: ${device.lastActiveAt}")
}
Unregister Device
devices.deleteDevice(id = "device_abc")
Topics
Subscribe to Topic
import dev.zyphr.sdk.api.TopicsApi
val topics = TopicsApi(client)
topics.subscribe(
topicId = "promotions",
subscriberId = "user_123"
)
Unsubscribe from Topic
topics.unsubscribe(
topicId = "promotions",
subscriberId = "user_123"
)
Send to Topic
push.sendToTopic(
topicId = "promotions",
SendPushRequest(
title = "Flash Sale!",
body = "50% off everything today"
)
)
Error Handling
All API methods are suspend functions that throw exceptions on failure:
import dev.zyphr.sdk.infrastructure.ClientException
import dev.zyphr.sdk.infrastructure.ServerException
try {
emails.sendEmail(request)
} catch (e: ClientException) {
// 4xx errors
println("Client error (${e.statusCode}): ${e.message}")
when (e.statusCode) {
400 -> println("Bad request — check your parameters")
401 -> println("Unauthorized — check your API key")
404 -> println("Resource not found")
429 -> println("Rate limited — slow down")
}
} catch (e: ServerException) {
// 5xx errors
println("Server error (${e.statusCode}): ${e.message}")
} catch (e: Exception) {
// Network errors
println("Network error: ${e.message}")
}
Retry with Exponential Backoff
suspend fun <T> withRetry(maxAttempts: Int = 3, operation: suspend () -> T): T {
var lastException: Exception? = null
repeat(maxAttempts) { attempt ->
try {
return operation()
} catch (e: ClientException) {
if (e.statusCode == 429) {
lastException = e
delay(2.0.pow(attempt).toLong() * 1000)
} else {
throw e // Don't retry other 4xx errors
}
} catch (e: ServerException) {
lastException = e
delay(2.0.pow(attempt).toLong() * 1000)
}
}
throw lastException!!
}
// Usage
val result = withRetry {
emails.sendEmail(request)
}
Coroutines
All API methods are suspend functions, so call them from whatever coroutine
scope your server framework gives you — a Ktor route handler, a Spring
suspend controller method, or an explicit CoroutineScope in a worker:
// Ktor — your app POSTs here; this handler calls Zyphr
post("/notifications") {
val body = call.receive<SendNotificationBody>()
val userId = call.principal<UserPrincipal>()!!.id // from the session, not the client
push.sendPush(
SendPushRequest(
userId = userId,
title = body.title,
body = body.body
)
)
call.respond(HttpStatusCode.Accepted)
}
Derive userId from the authenticated session rather than trusting a value the
app sent, so one user cannot send notifications as another.
Your Android app then calls your endpoint — with your own session token, not a Zyphr key — using whatever HTTP client the app already uses. The Zyphr SDK does not belong in the app's dependency graph.
Thread Safety
The SDK uses OkHttp's connection pool and is safe to share across threads and coroutine scopes. Create a single ApiClient instance on your server and reuse it:
// In your server's DI module — the key comes from the server environment.
// Do NOT source it from BuildConfig: that compiles the secret into the APK.
val zyphrClient = ApiClient(apiKey = System.getenv("ZYPHR_API_KEY"))
// Inject into your service/handler classes
val emailsApi = EmailsApi(zyphrClient)
val pushApi = PushApi(zyphrClient)
Requirements
- JVM 8+ (any Kotlin server runtime)
- Kotlin 1.8+
- OkHttp 4.x
The artifact also targets Android API 21+, but see the warning at the top of this page — an Android target must not carry a secret key.
Next Steps
- Client-side Auth —
ZyphrClientand the publishable key, the credential that is safe in a client app - Push Notifications for Android — Complete native Android push integration guide, including the backend-proxy shape
- Push Notifications API — Full push API reference
- Device Management — Managing registered devices