Skip to main content

Swift SDK (server-side)

This SDK holds a secret key — do not ship it in an iOS app

Every call in this guide authenticates with your secret API key (zy_live_* / zy_test_*). A secret key embedded in an app bundle is extractable from any IPA by anyone who downloads it, and it can send on your account, read your subscribers, and manage your configuration.

Use this SDK from server-side Swift, or from the backend your iOS 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 Swift surface, so a native iOS app needs a backend proxy.

The official Zyphr Swift SDK for server-side Swift and macOS, built with URLSession and Swift async/await. Auto-generated from the OpenAPI specification.

Installation

The Swift SDK is currently distributed by request — contact support@zyphr.dev for access. Public SPM and CocoaPods distribution is on the roadmap.

Configuration

Read the key from the environment on your server. Never inline it in source, and never let it reach an app bundle.

import ZyphrSDK

let config = Configuration(
apiKey: ProcessInfo.processInfo.environment["ZYPHR_API_KEY"]!,
basePath: "https://api.zyphr.dev/v1" // Optional custom endpoint
)

API Classes

The SDK exposes one API class per resource:

ClassDescription
EmailsAPISend and manage email messages
PushAPISend push notifications
SmsAPISend SMS text messages
InboxAPIIn-app notification inbox
SubscribersAPIManage subscriber profiles
DevicesAPIRegister push notification devices
TemplatesAPICreate and manage templates
TopicsAPIPub/sub topics
WebhooksAPIConfigure webhook endpoints

Emails

Send Email

let emails = EmailsAPI(configuration: config)

let result = try await emails.sendEmail(
sendEmailRequest: 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: ["welcome", "onboarding"],
metadata: ["userId": "user_123"]
)
)
print("Email sent: \(result.id)")

Send with Template

let result = try await emails.sendEmail(
sendEmailRequest: SendEmailRequest(
to: "user@example.com",
templateId: "welcome-email",
templateData: [
"name": "John",
"actionUrl": "https://yourapp.com/activate",
]
)
)

Get Email Status

let email = try await emails.getEmail(id: "msg_abc123")
print("Status: \(email.status)")

List Emails

let response = try await emails.listEmails(page: 1, perPage: 25)
for email in response.data {
print("\(email.id): \(email.status)")
}

Push Notifications

Send Push

let push = PushAPI(configuration: config)

try await push.sendPush(
sendPushRequest: SendPushRequest(
userId: "user_123",
title: "New Message",
body: "You have a new message",
data: ["messageId": "msg_456"]
)
)

Send to Specific Device

try await push.sendPush(
sendPushRequest: SendPushRequest(
deviceId: "device_abc",
title: "New Message",
body: "You have a new message"
)
)

Silent Push

try await push.sendPush(
sendPushRequest: SendPushRequest(
userId: "user_123",
contentAvailable: true,
data: ["type": "sync", "resource": "messages"]
)
)

Rich Notifications

try await push.sendPush(
sendPushRequest: 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: [
ActionButton(id: "view", title: "View"),
ActionButton(id: "dismiss", title: "Dismiss"),
]
)
)

SMS

let sms = SmsAPI(configuration: config)

let result = try await sms.sendSms(
sendSmsRequest: SendSmsRequest(
to: "+14155551234",
body: "Your verification code is 123456"
)
)
print("SMS sent: \(result.id)")

In-App Inbox

let inbox = InboxAPI(configuration: config)

try await inbox.sendInboxNotification(
sendInboxRequest: SendInboxRequest(
subscriberId: "user_123",
title: "New Comment",
body: "John commented on your post",
actionUrl: "/posts/123#comments"
)
)

Subscribers

Create Subscriber

let subscribers = SubscribersAPI(configuration: config)

let subscriber = try await subscribers.createSubscriber(
createSubscriberRequest: CreateSubscriberRequest(
externalId: "user_123",
email: "user@example.com",
phone: "+14155551234",
name: "John Doe",
metadata: ["plan": "pro"]
)
)

Get Subscriber

let subscriber = try await subscribers.getSubscriber(id: "user_123")
print("Name: \(subscriber.name ?? "unknown")")

Update Subscriber

try await subscribers.updateSubscriber(
id: "user_123",
updateSubscriberRequest: UpdateSubscriberRequest(
name: "Jane Doe",
metadata: ["plan": "enterprise"]
)
)

Delete Subscriber

try await subscribers.deleteSubscriber(id: "user_123")

Device Management

Device registration is server-side only

The device API authenticates with your secret key, which must never ship in an iOS bundle. Post the APNs token to your own backend and register from there, deriving userId from the authenticated session. See Device Management and the iOS push guide.

The snippets below show the request shape for reference — run them from your server, not the app.

Register Device

let devices = DevicesAPI(configuration: config)

try await devices.registerDevice(
registerDeviceRequest: RegisterDeviceRequest(
userId: "user_123",
platform: .ios,
token: apnsToken,
metadata: ["appVersion": "2.1.0"]
)
)

List Devices

let response = try await devices.listDevices(userId: "user_123")
for device in response.data {
print("\(device.platform): \(device.lastActiveAt)")
}

Unregister Device

try await devices.deleteDevice(id: "device_abc")

Topics

Subscribe to Topic

let topics = TopicsAPI(configuration: config)

try await topics.subscribe(
topicId: "promotions",
subscriberId: "user_123"
)

Unsubscribe from Topic

try await topics.unsubscribe(
topicId: "promotions",
subscriberId: "user_123"
)

Send to Topic

try await push.sendToTopic(
topicId: "promotions",
sendPushRequest: SendPushRequest(
title: "Flash Sale!",
body: "50% off everything today"
)
)

Error Handling

All API methods are async and throw errors on failure:

do {
try await emails.sendEmail(request)
} catch let error as ErrorResponse {
switch error {
case .error(let statusCode, let data, _, _):
let message = String(data: data ?? Data(), encoding: .utf8) ?? "Unknown error"
print("API Error (\(statusCode)): \(message)")

switch statusCode {
case 400: print("Bad request — check your parameters")
case 401: print("Unauthorized — check your API key")
case 404: print("Resource not found")
case 429: print("Rate limited — slow down")
default: print("Server error")
}
}
} catch {
print("Network error: \(error.localizedDescription)")
}

Retry with Exponential Backoff

func withRetry<T>(maxAttempts: Int = 3, operation: () async throws -> T) async throws -> T {
var lastError: Error?

for attempt in 0..<maxAttempts {
do {
return try await operation()
} catch let error as ErrorResponse {
if case .error(let statusCode, _, _, _) = error, statusCode == 429 || statusCode >= 500 {
lastError = error
let delay = pow(2.0, Double(attempt))
try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
continue
}
throw error // Don't retry 4xx errors (except 429)
}
}

throw lastError!
}

// Usage
let result = try await withRetry {
try await emails.sendEmail(request)
}

Thread Safety

All API classes are safe to use from any actor or thread. Each API call creates an independent URLSession task.

For SwiftUI, you can call SDK methods directly from Task blocks:

struct SendButton: View {
var body: some View {
Button("Send") {
Task {
try await emails.sendEmail(request)
}
}
}
}

Requirements

  • Swift 5.5+ on macOS 12.0+ or a Linux server runtime
  • Xcode 14.0+ (for macOS builds)

The SDK also builds for iOS 15.0+, but see the warning at the top of this page — an iOS target must not carry a secret key.

Next Steps