Skip to main content
Supported on: iOS SDK 1.0.61+ · Android SDK 1.0.33+ · React Native SDK 1.0.4+
In-app messages are configured in the AppDNA Console and displayed automatically by the SDK when trigger conditions are met. No code is required to show messages — the SDK evaluates triggers on every tracked event and presents the message if conditions match.

How It Works

  1. You create an in-app message in the Console with content, layout, and trigger rules.
  2. The message definition is synced to the SDK via the config bundle.
  3. On every track() call (including auto-tracked events), the SDK evaluates all active message triggers.
  4. If conditions match, the message is presented automatically.
In-app messages are fully server-driven. You can change the message content, trigger rules, and audience without an app update.

Message Types

TypeDescription
bannerSmall bar at the top or bottom of the screen
modalCentered overlay with a dimmed background
fullscreenFull-screen takeover
tooltipSmall popup anchored to a UI element

Triggers

Messages trigger based on events and optional conditions, configured in the Console:
  • Event match — e.g., trigger on workout_completed
  • Property conditions — e.g., duration >= 30
  • Frequencyonce, once_per_session, or every_time
  • Delay — wait N seconds after the trigger event before showing

Module Access

import { AppDNA } from '@appdna/react-native-sdk';

const messages = AppDNA.inAppMessages;

Module Methods

MethodSignatureDescription
suppressDisplaysuppressDisplay(suppress: boolean): Promise<void>Suppress or resume in-app messages
setDelegate`setDelegate(delegate: AppDNAInAppMessageDelegatenull): void`Set a delegate for message callbacks

Suppressing Messages

Suppress messages during critical flows like checkout or onboarding to avoid interrupting the user:
// Suppress during purchase flow
await AppDNA.inAppMessages.suppressDisplay(true);
startPurchaseFlow();

// Resume after purchase completes
async function onPurchaseComplete(): Promise<void> {
  await AppDNA.inAppMessages.suppressDisplay(false);
}

AppDNAInAppMessageDelegate

All 4 methods on this delegate are dispatched from the native MessageManager for every in-app message lifecycle event. Register your delegate via AppDNA.inAppMessages.setDelegate(...). shouldShowMessage is a true veto. The SDK calls it BEFORE constructing the view or tracking the in_app_message_shown analytics event. If you return false, the message is suppressed entirely — no view, no analytics. The default implementation returns true, so hosts that don’t override this method continue to see all messages. The 4 methods semantics:
  • onMessageShown(messageId, trigger) — fired after the SDK constructed the view AND emitted the in_app_message_shown analytics event. Use for app-side telemetry or routing logic.
  • onMessageAction(messageId, action, data) — fired when the user taps a CTA, deep-link, or custom action button. The data object carries action-specific payload (URL for deep links, custom keys you set in the Console).
  • onMessageDismissed(messageId) — fired exactly once when the message is dismissed by the user (close button, swipe, backdrop tap) or programmatically.
  • shouldShowMessage(messageId) — veto checked before display + analytics. Default returns true.
Implement the delegate to respond to message lifecycle events:
interface AppDNAInAppMessageDelegate {
  onMessageShown(messageId: string, trigger: string): void;

  onMessageAction(
    messageId: string,
    action: string,
    data: Record<string, unknown> | null,
  ): void;

  onMessageDismissed(messageId: string): void;

  /** Veto. Return false to suppress display. */
  shouldShowMessage?(messageId: string): boolean;
}

Example Implementation

import { AppDNA, AppDNAInAppMessageDelegate } from '@appdna/react-native-sdk';

const messageHandler: AppDNAInAppMessageDelegate = {
  onMessageShown(messageId, trigger) {
    console.log(`Message shown: ${messageId} (trigger: ${trigger})`);
  },

  onMessageAction(messageId, action, data) {
    const url = data?.url as string | undefined;
    if (url) {
      // Handle deep link or URL action
      navigate(url);
    }
  },

  onMessageDismissed(messageId) {
    console.log(`Message dismissed: ${messageId}`);
  },

  shouldShowMessage(messageId) {
    // Return false to prevent the message from being shown.
    return true;
  },
};

function navigate(url: string): void { /* ... */ }

AppDNA.inAppMessages.setDelegate(messageHandler);

Rich Media

In-app messages support rich media content configured in the Console:
  • Lottie animations — animated hero images or backgrounds
  • Video — inline video with optional autoplay and looping
  • Icon buttons — CTA buttons with icon references (Lucide, SF Symbols on iOS, Material on Android, or emoji)
  • Haptic feedback — triggered on message display or button taps
  • Particle effects — confetti, sparkles, or other effects on message actions
  • Blur backdrop — glassmorphism-style blurred background for modals
Rich media uses native iOS rendering (SwiftUI + CoreAnimation) and native Android rendering (Jetpack Compose + Rive) for full 60fps animations at native performance.

Auto-Tracked Events

EventTrigger
in_app_message_shownAn in-app message is displayed
in_app_message_clickedUser taps an action in the message
in_app_message_dismissedMessage is closed
in_app_message_received(Android pending-message channel) A message is received before display gating

Full Example

import { AppDNA, AppDNAInAppMessageDelegate } from '@appdna/react-native-sdk';

class AppCoordinator implements AppDNAInAppMessageDelegate {
  constructor() {
    AppDNA.inAppMessages.setDelegate(this);
  }

  async startOnboarding(): Promise<void> {
    // Suppress messages during onboarding
    await AppDNA.inAppMessages.suppressDisplay(true);
    this.presentOnboardingFlow();
  }

  async onboardingDidFinish(): Promise<void> {
    // Resume messages after onboarding
    await AppDNA.inAppMessages.suppressDisplay(false);
  }

  // AppDNAInAppMessageDelegate

  onMessageShown(messageId: string, trigger: string): void {
    // Optionally track in your own analytics
  }

  onMessageAction(
    messageId: string,
    action: string,
    data: Record<string, unknown> | null,
  ): void {
    const url = data?.url as string | undefined;
    if (url) {
      this.navigate(url);
    } else if (action === 'dismiss') {
      // no-op
    }
  }

  onMessageDismissed(messageId: string): void {
    // Message closed
  }

  shouldShowMessage(messageId: string): boolean {
    return true;
  }

  private navigate(url: string): void { /* ... */ }
  private presentOnboardingFlow(): void { /* ... */ }
}
In-app messages are created in the Console under Engagement > In-App Messages. Design the message, set trigger rules, and publish. The SDK handles everything else.