Official Swift SDK for the Lockally API — transactional email, contacts, and agent inboxes — for iOS, macOS, tvOS, and watchOS.
Lockally— the generated, fully-typed API client (async/await, URLSession).LockallyKit— the secure auth + ergonomics layer:TokenProvider, OAuth 2.1 PKCE, Keychain storage, automatic retries, idempotency keys, and cursor pagination.
Swift Package Manager
.package(url: "https://github.com/lockallyinc/lockally-swift.git", from: "0.1.0")CocoaPods
pod 'Lockally', '~> 0.1'A mobile app is a distributed binary — anything compiled in, including a
lk_live_… key, can be extracted. A leaked key with messages:send scope is an
open spam relay billed to you.
Rules of thumb
- Sending mail (OTP, verification, notifications) and contact sync →
BackendTokenProvider. Keep thelk_live_key on your server; hand the app short-lived, narrowly-scoped tokens. - A signed-in user reading their own mail (inbox/agent) →
OAuthPKCEProvider. StaticTokenProvideris for server-side / internal tools only. It warns if you pass alk_live_key.
import Lockally
import LockallyKit
// Recommended: your backend mints a short-lived token.
let provider = BackendTokenProvider(
endpoint: URL(string: "https://api.yourapp.com/lockally/token")!,
headers: ["Authorization": "Bearer \(yourUserSession)"]
)
let config = LockallyAPIConfiguration.lockally(provider: provider)The returned config already has retries, idempotency, and auth wired in — pass
it to any generated API, e.g. MessagesAPI, ContactsAPI, InboxesAPI.
Send from your backend, not the device. Your token endpoint returns a token
scoped to messages:send only; the app then triggers the send:
let api = SendAPI(apiConfiguration: config)
let body = V1SendPostRequest(
from: "[email protected]",
to: ["[email protected]"],
templateId: "otp-code", // manage templates in the console
variables: ["code": otp, "ttl": "10"]
)
// A stable Idempotency-Key is attached automatically, so a retry never
// double-sends the same code.
_ = try await api.v1SendPost(v1SendPostRequest: body)Use a backend-minted token scoped to contacts:read/contacts:write, then walk
every page with the paginator:
let contactsAPI = ContactsAPI(apiConfiguration: config)
let all = try await Paginator<Contact> { cursor in
let page = try await contactsAPI.v1ContactsGet(cursor: cursor)
return Page(items: page.data ?? [], nextCursor: page.nextCursor)
}.collect()Register the device's APNs token with your backend; your backend decides per event whether to push, email (via Lockally), or both. The SDK drives the email leg — delivery orchestration stays server-side where the key lives.
The one interactive user-token flow the API issues today (inboxes:read /
inboxes:write):
let oauth = OAuthPKCEProvider(config: OAuthConfig(
clientID: "your-registered-client-id",
redirectURI: "yourapp://oauth/callback"
))
try await oauth.signIn(anchor: view.window!) // opens the system browser
let inboxCfg = LockallyAPIConfiguration.lockally(provider: oauth)
let inbox = InboxesAPI(apiConfiguration: inboxCfg)Failures surface as LockallyError with status / code / message /
requestId parsed from the API's problem+json, so you can branch without
string-matching and log requestId for support.
MIT — see LICENSE.