What you get

Flutter draws its own widgets, which is great for your app and a poor fit for a support screen that has to handle photos, files, push taps, right-to-left languages and the keyboard the way the platform does. The devreply plugin doesn't draw a chat in Dart. It opens DevReply's native iOS and Android SDKs and only passes calls through.

Your users get a home screen with start buttons (bug, billing, idea, question), their conversations, photos and files, name first, optional email, and “we got it” with your reply time. The chat follows the phone's language (34 languages, Hebrew and Arabic right to left). An unread bubble floats over your app, and the unread count is a Dart stream. It needs iOS 17 and Android 8 (API 26).

1. Install from pub.dev

flutter pub add devreply

2. Configure once, present from a button

Create an app in the DevReply dashboard. It gives you a public key for iOS and one for Android, each starting with pk_. They're safe to ship; a secret key (sk_…) never goes in an app.

import 'package:devreply/devreply.dart';

// Once, at startup: your public keys from the dashboard (safe to ship). Never a secret key (sk_…).
await DevReply.configure(ios: 'pk_…', android: 'pk_…');

// From any button
DevReply.present();                          // or DevReply.present(DevReplyCategory.bug)

In practice, configure in main and put the button wherever users look for help:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await DevReply.configure(ios: 'pk_…', android: 'pk_…');
  runApp(const MyApp());
}

// In your settings screen
ElevatedButton(
  onPressed: () => DevReply.present(),
  child: const Text('Message the developer'),
)

The optional calls give your team the user's name and email and attributes like their plan, and let you draw your own unread badge:

DevReply.setUser(name: 'Ana', email: '[email protected]');
DevReply.setAttributes({'plan': 'pro', 'trial': false});
DevReply.unreadCountChanges.listen((count) => setState(() => unread = count));
DevReply.setShowsUnreadBubble(false);        // if you show the count yourself

Your team can switch the chat off in the dashboard without a release. Then present returns false and shows nothing, and await DevReply.isAvailable tells you up front, to hide your own button.

3. Open it with a draft and context

Where a user is most likely to give up, like a failed purchase, open a new conversation with text already in the composer and context for your team. The user sees the draft and sends it; nothing is sent on its own. The attributes (text, number or true/false, up to 20) show in your inbox as “Opened with”:

await DevReply.present(DevReplyCategory.billing, const DevReplyPresentOptions(
  message: "My purchase didn't go through",
  attributes: {'source': 'paywall', 'rc_error_code': 'PURCHASE_NOT_ALLOWED'},
));

4. Sign-in, sign-out and account deletion

await DevReply.login(user.id);               // after sign-in: your own id for the user, never an email or a secret
await DevReply.logout();                     // on every sign-out and account switch
final ok = await DevReply.deleteUser();      // in your delete-account flow; false = queued, retried until done

login labels the user for your team. logout makes the device forget the chat, so the next person starts empty. deleteUser deletes their name, email, attributes, conversations, messages and files, as Apple requires account deletion in the app, and retries until the server confirms.

5. Push with firebase_messaging

Your app keeps its firebase_messaging setup. DevReply never takes over your notification handling: you pass it the token, the taps and, on Android, its own messages.

final token = Platform.isIOS
    ? await FirebaseMessaging.instance.getAPNSToken()   // iOS: the APNs token
    : await FirebaseMessaging.instance.getToken();      // Android: the FCM token
if (token != null) await DevReply.registerPushToken(token);
FirebaseMessaging.instance.onTokenRefresh.listen((t) { if (Platform.isAndroid) DevReply.registerPushToken(t); });

// Android: DevReply shows its own notification; a tap opens the conversation.
FirebaseMessaging.onMessage.listen((m) async { if (await DevReply.handlePush(m.data)) return; /* yours */ });
// and first thing in your onBackgroundMessage handler:
//   if (await DevReply.handlePush(message.data)) return;

// Taps (iOS, and Android notifications firebase_messaging shows), including the one that launched the app.
FirebaseMessaging.onMessageOpenedApp.listen((m) async { if (await DevReply.handleNotificationOpened(m.data)) return; /* yours */ });
final initial = await FirebaseMessaging.instance.getInitialMessage();
if (initial != null) await DevReply.handleNotificationOpened(initial.data);

handleNotificationOpened returns false for your own notifications. Upload your push keys in the dashboard (the APNs key; the Firebase service account for Android). The chat asks for the notification permission only after the user's first message, and the dashboard's push card shows “✓ Taps open the chat” once a tap opened a conversation.

On Pro, users who left an email and haven't read a reply get it by email, with a “Reply in the app” button that opens yourapp://devreply?devreply=<conversation>. Add the scheme (iOS CFBundleURLTypes in Info.plist; Android a VIEW intent filter with android:scheme="yourapp" android:host="devreply" on MainActivity) and set yourapp://devreply as the deep link in the dashboard. The plugin catches these links itself; if a links package swallows them first, pass them on with DevReply.handle(url). With go_router and Flutter deep linking on, redirect /devreply to where the user already is.

What you see on your side

Each message lands in your DevReply inbox in seconds, tagged with the start button the user picked, with their name, device, OS, app version, language, your attributes and the “Opened with” context. You reply from the dashboard with your own name and photo; it reaches them live while the chat is open, and as a push or the unread bubble when it isn't. Resolve it when it's done; a reply reopens it.

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 Flutter project. Or connect the agent over MCP, where the setup tools are free on every plan, including uploading your push keys:

claude mcp add --transport http devreply https://api.devreply.com/mcp

The reference for agents is llms.txt.

More guides

Fix it before it's a review.

Free to start, no card: unlimited apps, users and teammates, 50 conversations a month. Pro is $49 a month when your agent takes over.