> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tappd.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile In-App Messages: Banners, Popups, and Modals

> Display personalized banners, popups, and modals in your React Native app using Tappd's MessageRenderer component and automatic trigger evaluation.

In-app messages let you deliver personalized content — promotional banners, onboarding prompts, important alerts — directly inside your React Native app without requiring a separate release. You create and configure messages in the Tappd dashboard, and the SDK automatically fetches, evaluates trigger conditions, and hands them off to your app to render using the built-in `MessageRenderer` component.

## Configuration

Enable in-app messages when you initialize the SDK:

```javascript theme={null}
const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  apiUrl: 'https://sdk.gotappd.com/api/v1/sdk',
  enableInAppMessages: true,
  autoDisplayMessages: true,
  messagePollingInterval: 30,
});
```

<ParamField body="enableInAppMessages" type="boolean" default="true">
  Enable or disable in-app message rendering in your app. Set to `false` if you
  want to fully suppress all in-app messages.
</ParamField>

<ParamField body="autoDisplayMessages" type="boolean" default="true">
  When `true`, the SDK automatically fetches pending messages for the identified
  user, evaluates trigger conditions, and calls your render callback when a
  message is ready to display.
</ParamField>

<ParamField body="messagePollingInterval" type="number" default="30">
  How often (in seconds) the SDK polls for new messages. Lower values give more
  timely delivery; higher values reduce network activity.
</ParamField>

## React Native Integration

To render messages, register a callback with the SDK so it can hand off a message object to your component tree whenever one is ready to display.

### Basic Setup

```javascript theme={null}
import React, { useState, useEffect } from 'react';
import { View } from 'react-native';
import TappdSDK from '@tappd/mobile-sdk';
import { MessageRenderer } from '@tappd/mobile-sdk/src/renderers/MessageRenderer';

const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  enableInAppMessages: true,
  autoDisplayMessages: true,
});

function App() {
  const [currentMessage, setCurrentMessage] = useState(null); // InAppMessage | null

  useEffect(() => {
    // Register the callback the SDK calls when a message is ready
    tappd.setMessageRenderCallback((message) => {
      setCurrentMessage(message);
    });

    return () => {
      tappd.setMessageRenderCallback(() => {});
    };
  }, []);

  const handleDismiss = async (messageId) => {
    await tappd.dismissMessage(messageId);
    setCurrentMessage(null);
  };

  const handleButtonClick = async (messageId, link, text) => {
    console.log('Button clicked:', { messageId, link, text });
    await handleDismiss(messageId);
  };

  return (
    <View style={{ flex: 1 }}>
      {/* Your app content */}

      {currentMessage && (
        <MessageRenderer
          message={currentMessage}
          onDismiss={handleDismiss}
          onButtonClick={handleButtonClick}
        />
      )}
    </View>
  );
}
```

## Automatic Display

When `autoDisplayMessages` is `true`, the SDK handles the full delivery pipeline for you:

<Steps>
  <Step title="Fetch pending messages">
    After `identify()` is called, the SDK automatically retrieves any pending
    messages for that user from the Tappd API.
  </Step>

  <Step title="Evaluate trigger conditions">
    Each message is evaluated against its configured trigger (immediate, timed
    delay, or event-based) and expiration date.
  </Step>

  <Step title="Invoke your render callback">
    When a message is ready to display, the SDK calls the function you
    registered with `setMessageRenderCallback()`, passing the message object.
  </Step>

  <Step title="Track interactions automatically">
    The SDK automatically tracks `message.viewed`, `message.clicked`, and
    `message.dismissed` events as the user interacts with the message.
  </Step>

  <Step title="Poll for new messages">
    The SDK continues polling at the interval defined by
    `messagePollingInterval` to pick up newly published messages.
  </Step>
</Steps>

```javascript theme={null}
const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  enableInAppMessages: true,
  autoDisplayMessages: true,
  messagePollingInterval: 30,
});

// Identify the user — automatic message fetching begins here
await tappd.identify({
  external_id: 'user_123',
  email: 'john@example.com',
  name: 'John Doe',
});

// Register the render callback
tappd.setMessageRenderCallback((message) => {
  setCurrentMessage(message);
});
```

## Manual Control

If you prefer to control when messages are fetched and displayed, you can bypass automatic display and call these methods directly.

<Tabs>
  <Tab title="Fetch messages">
    ```javascript theme={null}
    // Fetch all pending messages for the current user
    const messages = await tappd.getInAppMessages();
    console.log(`Found ${messages.length} pending messages`);
    ```
  </Tab>

  <Tab title="Display a specific message">
    ```javascript theme={null}
    const messages = await tappd.getInAppMessages();

    if (messages.length > 0) {
      await tappd.displayInAppMessage(messages[0]);
    }
    ```
  </Tab>

  <Tab title="Display all pending messages">
    ```javascript theme={null}
    // Fetch and display all messages that are ready to show
    await tappd.displayPendingMessages();
    ```
  </Tab>

  <Tab title="Dismiss a message">
    ```javascript theme={null}
    // Dismiss a message by ID — also tracks the dismissal event
    await tappd.dismissMessage('message_id_123');
    ```
  </Tab>
</Tabs>

## Message Types

<Accordion title="Banner">
  Banners are fixed-position messages that slide in at the top or bottom of the
  screen using absolute positioning.

  * **Position**: Top or bottom of the screen
  * **Animation**: Slide
  * **Dismissible**: Optional dismiss button
  * **Use cases**: Announcements, promotions, time-sensitive notifications
</Accordion>

<Accordion title="Popup">
  Popups are centered modal dialogs built on React Native's `Modal` component,
  ideal for drawing focused attention.

  * **Size**: \~90% of screen width (max 500 px)
  * **Overlay**: Optional semi-transparent background
  * **Animation**: Fade or scale
  * **Use cases**: Important alerts, confirmations, calls to action
</Accordion>

<Accordion title="Modal">
  Modals are larger centered dialogs with more room for rich content such as
  multi-step flows or detailed information.

  * **Size**: \~85% of screen width (max 600 px)
  * **Overlay**: Optional semi-transparent background
  * **Animation**: Fade or scale
  * **Use cases**: Detailed content, forms, multi-step flows
</Accordion>

## Message Blocks

Messages are composed of one or more content blocks, each rendered as a native React Native component.

<Accordion title="Image Block">
  Renders images using React Native's `Image` component.

  | Property    | Description                          |
  | ----------- | ------------------------------------ |
  | `url`       | Image URL                            |
  | `alt`       | Alt text for accessibility           |
  | `width`     | Image width                          |
  | `height`    | Image height                         |
  | `alignment` | `left`, `center`, `right`, or `full` |
</Accordion>

<Accordion title="Text Block">
  Renders text content using React Native's `Text` component.

  | Property     | Description                                       |
  | ------------ | ------------------------------------------------- |
  | `content`    | Text content to display                           |
  | `fontSize`   | `sm`, `base`, `lg`, `xl`, `2xl`, or a custom size |
  | `fontWeight` | `normal` or `bold`                                |
  | `color`      | Text color (hex, rgb, or named color)             |
  | `alignment`  | `left`, `center`, `right`, or `justify`           |
</Accordion>

<Accordion title="Button Block">
  Renders a tappable button using React Native's `TouchableOpacity`.

  | Property          | Description                                   |
  | ----------------- | --------------------------------------------- |
  | `text`            | Button label                                  |
  | `link`            | URL opened via `Linking.openURL()`            |
  | `textColor`       | Button text color                             |
  | `backgroundColor` | Button background color                       |
  | `linkBehavior`    | `browser` (open URL), `in_app`, or `deeplink` |
  | `alignment`       | `left`, `center`, or `right`                  |
</Accordion>

<Accordion title="HTML Block">
  Renders arbitrary HTML content using `react-native-render-html`.

  | Property  | Description                |
  | --------- | -------------------------- |
  | `content` | Full HTML string to render |

  <Note>
    `react-native-render-html` is bundled inside the Tappd Mobile SDK. You do
    **not** need to install it separately in your project.
  </Note>
</Accordion>

## Event Tracking

The SDK tracks all standard message interactions automatically:

| Event               | Triggered when                        |
| ------------------- | ------------------------------------- |
| `message.viewed`    | The message is displayed to the user  |
| `message.clicked`   | A button inside the message is tapped |
| `message.dismissed` | The user dismisses the message        |

For custom interactions, call `trackMessageEvent()` directly:

```javascript theme={null}
await tappd.trackMessageEvent('message_id_123', 'clicked', {
  buttonText: 'Sign Up',
  buttonLink: 'https://example.com/signup',
});
```

## Integration with React Navigation

Place `MessageRenderer` at the root of your navigation tree so messages appear above all screens, regardless of which route is currently active:

```javascript theme={null}
import { NavigationContainer } from '@react-navigation/native';
import { MessageRenderer } from '@tappd/mobile-sdk/src/renderers/MessageRenderer';

function App() {
  const [currentMessage, setCurrentMessage] = useState(null);

  useEffect(() => {
    tappd.setMessageRenderCallback((message) => {
      setCurrentMessage(message);
    });
  }, []);

  return (
    <NavigationContainer>
      {/* Your navigation structure */}

      {currentMessage && (
        <MessageRenderer
          message={currentMessage}
          onDismiss={(id) => {
            tappd.dismissMessage(id);
            setCurrentMessage(null);
          }}
        />
      )}
    </NavigationContainer>
  );
}
```

## Complete Example

```javascript theme={null}
import React, { useState, useEffect } from 'react';
import { View, StyleSheet, Linking } from 'react-native';
import TappdSDK from '@tappd/mobile-sdk';
import { MessageRenderer } from '@tappd/mobile-sdk/src/renderers/MessageRenderer';

const tappd = new TappdSDK({
  appId: 'YOUR_APP_ID',
  apiUrl: 'https://sdk.gotappd.com/api/v1/sdk',
  enableInAppMessages: true,
  autoDisplayMessages: true,
  messagePollingInterval: 30,
  debug: true,
});

export default function App() {
  const [currentMessage, setCurrentMessage] = useState(null); // InAppMessage | null

  useEffect(() => {
    tappd.setMessageRenderCallback((message) => {
      setCurrentMessage(message);
    });

    tappd.identify({
      external_id: 'user_123',
      email: 'john@example.com',
      name: 'John Doe',
    });

    return () => {
      // Clear the callback on unmount
      tappd.setMessageRenderCallback(() => {});
    };
  }, []);

  const handleDismiss = async (messageId) => {
    await tappd.dismissMessage(messageId);
    setCurrentMessage(null);
  };

  const handleButtonClick = async (messageId, link, text) => {
    if (link && link !== '#') {
      Linking.openURL(link);
    }
    await handleDismiss(messageId);
  };

  return (
    <View style={styles.container}>
      {/* Your app content */}

      {currentMessage && (
        <MessageRenderer
          message={currentMessage}
          onDismiss={handleDismiss}
          onButtonClick={handleButtonClick}
        />
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});
```

## API Reference

| Method                                               | Returns                   | Description                                                              |
| ---------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------ |
| `setMessageRenderCallback(callback)`                 | `void`                    | Registers the function the SDK calls when a message is ready to display. |
| `getInAppMessages()`                                 | `Promise<InAppMessage[]>` | Fetches all pending in-app messages for the current user.                |
| `displayInAppMessage(message)`                       | `Promise<void>`           | Displays a specific message by calling the registered render callback.   |
| `displayPendingMessages()`                           | `Promise<void>`           | Fetches and displays all messages that are ready to show.                |
| `dismissMessage(messageId)`                          | `Promise<void>`           | Dismisses a message and tracks the `message.dismissed` event.            |
| `trackMessageEvent(messageId, eventType, metadata?)` | `Promise<void>`           | Manually tracks a message interaction with optional metadata.            |

## Troubleshooting

<Accordion title="Messages not displaying">
  Work through this checklist if messages aren't appearing in your app:

  1. Confirm `enableInAppMessages` is set to `true` in your SDK config.
  2. Verify you have called `setMessageRenderCallback()` in your root component.
  3. Make sure `identify()` has been called — messages are user-specific.
  4. Enable `debug: true` in the SDK config and check the console for errors.
  5. Confirm the messages are published in your Tappd dashboard.
  6. Check that any configured trigger conditions are met.
</Accordion>

<Accordion title="Messages displaying too frequently">
  If the same message keeps reappearing:

  1. Increase `messagePollingInterval` to poll less aggressively.
  2. Check the expiration settings on the message in the dashboard.
  3. Ensure `dismissMessage()` is being called correctly when a message is dismissed so the SDK records the dismissal.
</Accordion>

<Accordion title="Styling issues">
  If message styles look wrong or conflict with your app:

  1. Check for `StyleSheet` conflicts between your app styles and the `MessageRenderer` component.
  2. Ensure `MessageRenderer` is rendered at the correct level in the component tree (ideally at the root, above your navigation structure).
  3. Test on both iOS and Android — some styles, such as shadows and font rendering, differ between platforms.
</Accordion>
