- Why in the app
- Install
- Configure
- The button
- The user
- From the paywall
- Push and email
- Your inbox
- Let your agent do it
Why the chat belongs inside the app
When something breaks in an iOS app, most users don't go looking for a support email. They close the app, or they open the App Store and leave a review. A chat one tap away from the problem catches them first, and it can attach what you'd otherwise have to ask for: the device, the iOS version, the app version and anything your app knows, like their plan.
DevReply's iOS SDK is written in Swift and SwiftUI, with no dependencies. It needs iOS 17 and Swift 6 / Xcode 16 or later. Your users get a home screen with start buttons (bug, billing, idea, question), their conversations, photos and files, and the chat in their phone's language (34 languages, Hebrew and Arabic right to left). There's no web view in it.
1. Add the Swift package
In Xcode, add the package https://github.com/Code-Axolot-Team/devreply-ios with the product DevReply, “Up to Next Major” from 0.5.0. In a Package.swift:
// Package.swift
.package(url: "https://github.com/Code-Axolot-Team/devreply-ios", from: "0.5.0")
2. Configure it once, at launch
Create an app in the DevReply dashboard and copy its iOS public key. It starts with pk_ and is safe to ship in your app. A secret key (sk_…) never goes in an app.
import SwiftUI
import DevReply
@main
struct MyApp: App {
init() {
// Once, at launch (your iOS public key from the dashboard; it's safe to ship)
DevReply.configure("pk_…")
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
3. Add a “Message the developer” button
Call present() from any button. It opens the chat over whatever is on screen:
Button("Message the developer") {
DevReply.present() // or present(category: .bug)
}
If you'd rather drive it from SwiftUI state, use the view modifier:
struct SettingsView: View {
@State private var showChat = false
var body: some View {
List {
if DevReply.isAvailable {
Button("Message the developer") { showChat = true }
}
}
.devReplyMessenger(isPresented: $showChat)
}
}
Your team can switch the chat off in the dashboard, say for a holiday, without a release. Then present returns false and shows nothing, and DevReply.isAvailable lets you hide your own button.
4. Tell it who the user is
All of this is optional. A name and email save the user typing them, and attributes show up next to every conversation in your inbox:
DevReply.setUser(name: "Ana", email: "[email protected]")
DevReply.setAttributes(["plan": "pro", "trial": false])
If your app has accounts, mirror sign-in, sign-out and account deletion:
DevReply.login(userID: account.id) // after sign-in: your own id for the user, never an email or a secret
DevReply.logout() // on every sign-out and account switch
let deleted = await DevReply.deleteUser() // in your delete-account flow; false = queued, retried until done
logout makes the device forget the chat, so the next person on a shared phone starts empty. deleteUser deletes the user's name, email, attributes, conversations and files, which covers Apple's rule that account deletion happens in the app.
5. Open it from the paywall, with context
The most expensive messages come from a failed purchase. Open the chat right there, with a draft in the composer and context for your team. The user sees the draft and sends it; nothing is sent on its own. askName: false skips the name form, so it's one tap from the error to the message:
DevReply.present(
category: .billing,
message: "My purchase didn't go through",
attributes: ["source": "paywall", "rc_error_code": "PURCHASE_NOT_ALLOWED"],
askName: false
)
The attributes go with that one conversation and show in the inbox as “Opened with”, so the message arrives already diagnosed.
6. Replies that reach them: push and email
While the chat is open, replies arrive live. When it isn't, DevReply shows an unread bubble over your app, sends a push, and on Pro emails users who left an email and haven't read the reply. For push, pass the device token from your app delegate:
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
DevReply.registerPush(deviceToken)
}
}
// In your App: @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
Then upload your APNs key in the dashboard. DevReply never takes over your notification handling: it sets its own delegate only when your app has none. If you have a UNUserNotificationCenterDelegate, forward with DevReply.presentationOptions(for:) and DevReply.handleNotificationResponse(_:); if a push library hands you a tapped notification's data, pass it to DevReply.handleNotificationOpened(userInfo:). The dashboard's push card shows “✓ Taps open the chat” once it works.
The email has a “Reply in the app” button that opens yourapp://devreply?devreply=<conversation>. Add a URL scheme, set yourapp://devreply as the deep link in the dashboard, and let DevReply look at links first:
.onOpenURL { url in
if DevReply.handle(url) { return }
// your own links
}
What you see on your side
The message lands in your inbox in seconds, tagged with the start button the user picked, and conversations waiting on you sit in red at the top. Next to it: their name, device, iOS and app version, language, your attributes and the “Opened with” context. You reply from the dashboard with your own name and photo, in Markdown (bold, lists, links). When it's sorted, resolve it; the user sees it's done and can reopen it by replying.
Let your coding agent install it
Every app in DevReply has a setup guide written for coding agents, with your own keys in it: the dashboard, the app → Settings → “Add DevReply to your app”. Hand it to Claude Code, Cursor or Codex in your repository and it adds the package, the configure call, the button, push and the deep link, then checks the install with the guide's status URL. You can also connect the agent over MCP; the setup tools are free on every plan:
claude mcp add --transport http devreply https://api.devreply.com/mcp
On Pro, the same connection lets your agent read and sort the inbox and draft replies you approve. The full list for agents is in llms.txt.