ابرووا
شروع رایگان
☰ فهرست مستندات · صندوق
به‌زودی. Abrova Push به‌زودی فعال می‌شود. این صفحه توضیح می‌دهد چطور کار خواهد کرد؛ هنوز قابل استفاده نیست.
این صفحه هنوز ترجمه نشده — نسخه‌ی انگلیسی نمایش داده می‌شود. English

Message Inbox

Persistent message center — store messages users can revisit. Send to user/users/broadcast, message fields, SDK integration on Flutter/RN/Android/Web, building an inbox UI.

به‌روزرسانی: ۵ آبان ۱۴۰۵

A persistent message center for your users. Unlike push notifications, inbox messages are stored and can be retrieved later — users never miss important updates.

Why Use Message Inbox?#

Persistent Storage

Messages are stored and accessible anytime, unlike ephemeral push notifications

Read Tracking

Know when users read your messages with built-in analytics

Category Filtering

Organize messages by category (promotions, updates, alerts)

User Control

Users can archive, delete, or mark messages as read

Use Cases#

Announcements

Product updates, new features, company news that users should be able to reference later.

Promotions

Special offers, discounts, and deals with expiration dates that users can redeem at their convenience.

Transactional

Order updates, account alerts, and important notifications that need to be preserved.

Using the Dashboard#

In Abrova Console, open AbrovaPush → Inbox. It provides four ways to send messages:

Send to User

Send a message to a specific user by their ID

Send to Device

Send a message to a specific device by its device ID

Send to Multiple

Send to multiple users by providing a comma-separated list of user IDs

Broadcast to All

Send to all users of your app at once

Send to User#

Node.js SDK

typescript
import { AbrovaPush } from '@abrova/push-node';

const abrovaPush = new AbrovaPush({
  apiKey: 'ab_live_your_api_key',
  serverSecret: 'ab_srv_your_server_secret',
});

await abrovaPush.inbox.sendToUser({
  userId: 'user_123',
  content: {
    title: 'New Promotion!',
    body: 'Get 20% off your next purchase',
    imageUrl: 'https://example.com/promo.jpg',
    deepLink: 'myapp://promotions/123',
  },
  category: 'promotions',
  expiresAt: '2024-02-01T00:00:00Z',
});

REST API

bash
curl -X POST "https://push-api.abrova.ir/inbox/api/send-to-user" \
  -H "x-api-key: ab_live_your_api_key" \
  -H "x-server-secret: ab_srv_your_server_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user_123",
    "content": {
      "title": "New Promotion!",
      "body": "Get 20% off your next purchase",
      "imageUrl": "https://example.com/promo.jpg",
      "deepLink": "myapp://promotions/123"
    },
    "category": "promotions",
    "expiresAt": "2024-02-01T00:00:00Z"
  }'

Send to Multiple Users#

Node.js SDK

typescript
await abrovaPush.inbox.sendToUsers({
  userIds: ['user_123', 'user_456', 'user_789'],
  content: {
    title: 'Flash Sale!',
    body: 'Limited time offer for VIP members',
  },
  category: 'promotions',
});

REST API

bash
curl -X POST "https://push-api.abrova.ir/inbox/api/send-to-users" \
  -H "x-api-key: ab_live_your_api_key" \
  -H "x-server-secret: ab_srv_your_server_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "userIds": ["user_123", "user_456", "user_789"],
    "content": {
      "title": "Flash Sale!",
      "body": "Limited time offer for VIP members"
    },
    "category": "promotions"
  }'

Broadcast to All Users#

Node.js SDK

typescript
await abrovaPush.inbox.broadcast({
  content: {
    title: 'App Update Available',
    body: 'We have released new features! Check them out.',
  },
  category: 'announcements',
});

REST API

bash
curl -X POST "https://push-api.abrova.ir/inbox/api/broadcast" \
  -H "x-api-key: ab_live_your_api_key" \
  -H "x-server-secret: ab_srv_your_server_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "content": {
      "title": "App Update Available",
      "body": "We have released new features! Check them out."
    },
    "category": "announcements"
  }'

Message Fields#

OptionTypeRequiredDescription
content.titlestringYesMessage title
content.bodystringYesMessage body
content.imageUrlstringNoImage URL to display with message
content.iconUrlstringNoSmall icon/avatar for the message
content.deepLinkstringNoDeep link to open when message is tapped
content.dataobjectNoCustom key/value data for your app
localizationsarrayNoPer-locale content: [{ locale, content }]
categorystringNoCategory for filtering (e.g., "promotions", "orders")
expiresAtISO 8601NoExpiration date (message hidden after this date)
Signed user tokens.

Abrova Console has a project setting, Require signed user tokens for the inbox (Project Settings → Abrova Push). With it on, the inbox read/update routes the SDKs call only serve a user's messages when the request carries an x-user-token minted by your server; otherwise they return 401 with code: "token_required". The Push client SDKs below do not send this header yet, so leave the setting off unless your app calls the inbox API itself with the token. Device-only inboxes and push delivery are not affected.

Flutter#

dart
// Fetch inbox messages
final response = await AbrovaPush.getInboxMessages(
  status: InboxMessageStatus.unread, // omit for all; unread, read, archived
  category: 'promotions', // optional filter
  limit: 20,
  offset: 0,
);
print('Total: ${response.total}, Unread: ${response.unreadCount}');

// Display messages in your UI
for (final message in response.messages) {
  print('Title: ${message.content.title}');
  print('Body: ${message.content.body}');
  print('Status: ${message.status}');
  print('Read at: ${message.readAt}');
}

// Mark message as read
await AbrovaPush.markInboxMessageAsRead(message.id);

// Mark multiple messages with a status
await AbrovaPush.markMultipleInboxMessages(
  ['msg_1', 'msg_2', 'msg_3'],
  InboxMessageStatus.read,
);

// Mark all messages as read
await AbrovaPush.markAllInboxMessagesAsRead();

// Archive a message
await AbrovaPush.archiveInboxMessage(message.id);

// Delete a message
await AbrovaPush.deleteInboxMessage(message.id);

// Get unread count for badge (cached; fetchInboxUnreadCount() asks the server)
final unreadCount = await AbrovaPush.getInboxUnreadCount();
print('Unread messages: $unreadCount');

// Listen to new inbox messages
AbrovaPush.onInboxMessage((message) {
  print('New inbox message: ${message.content.title}');
  // Update your UI or show a badge
});

React Native#

typescript
import AbrovaPush, { InboxMessageStatus } from 'abrova-push-react-native';

// Fetch inbox messages
const { messages, total, unreadCount } = await AbrovaPush.getInboxMessages({
  status: InboxMessageStatus.UNREAD, // omit for all; UNREAD, READ, ARCHIVED
  category: 'promotions', // optional filter
  limit: 20,
  offset: 0,
});

// Display in FlatList or custom UI
messages.forEach(message => {
  console.log('Title:', message.content.title);
  console.log('Body:', message.content.body);
  console.log('Status:', message.status);
});

// Mark message as read
await AbrovaPush.markInboxMessageAsRead(message.id);

// Mark multiple messages with a status
await AbrovaPush.markMultipleInboxMessages(['msg_1', 'msg_2'], InboxMessageStatus.READ);

// Mark all messages as read
await AbrovaPush.markAllInboxMessagesAsRead();

// Archive a message
await AbrovaPush.archiveInboxMessage(message.id);

// Delete a message
await AbrovaPush.deleteInboxMessage(message.id);

// Get unread count for badge (cached; fetchInboxUnreadCount() asks the server)
const badgeCount = await AbrovaPush.getInboxUnreadCount();
console.log('Unread:', badgeCount);

// Cached messages, no network call
const cached = await AbrovaPush.getCachedInboxMessages();

// Note: the React Native wrapper has no live inbox listener yet.
// Refresh with getInboxMessages() / fetchInboxUnreadCount() (e.g. on screen focus).

Android#

kotlin
// Fetch inbox messages
val filter = InboxFilter(
    status = InboxMessageStatus.UNREAD, // null for all
    category = "promotions", // optional
    limit = 20,
    offset = 0
)

AbrovaPush.getInboxMessages(
    filter = filter,
    onSuccess = { response ->
        Log.d("Inbox", "Total: ${response.total}, Unread: ${response.unreadCount}")
        response.messages.forEach { message ->
            Log.d("Inbox", "Title: ${message.content.title}")
            Log.d("Inbox", "Body: ${message.content.body}")
            Log.d("Inbox", "Status: ${message.status}")
        }
    },
    onError = { error ->
        Log.e("Inbox", "Failed to fetch: $error")
    }
)

// Mark message as read
AbrovaPush.markInboxMessageAsRead(
    messageId = message.id,
    onSuccess = { Log.d("Inbox", "Marked as read") },
    onError = { error -> Log.e("Inbox", "Error: $error") }
)

// Mark all messages as read
AbrovaPush.markAllInboxMessagesAsRead(
    onSuccess = { Log.d("Inbox", "All marked as read") },
    onError = { error -> Log.e("Inbox", "Error: $error") }
)

// Archive a message
AbrovaPush.archiveInboxMessage(
    messageId = message.id,
    onSuccess = { Log.d("Inbox", "Archived") },
    onError = { error -> Log.e("Inbox", "Error: $error") }
)

// Delete a message
AbrovaPush.deleteInboxMessage(
    messageId = message.id,
    onSuccess = { Log.d("Inbox", "Deleted") },
    onError = { error -> Log.e("Inbox", "Error: $error") }
)

// Get unread count (from cache, synchronous)
val unreadCount = AbrovaPush.getInboxUnreadCount()
Log.d("Inbox", "Unread: $unreadCount")

// Fetch unread count from server
AbrovaPush.fetchInboxUnreadCount(
    onSuccess = { count -> updateBadge(count) },
    onError = { error -> Log.e("Inbox", "Error: $error") }
)

// Get cached messages (no network call)
val cachedMessages = AbrovaPush.getCachedInboxMessages()

// Set callback for new inbox messages
AbrovaPush.setInboxCallback(object : InboxCallback {
    override fun onMessageReceived(message: InboxMessage) {
        Log.d("Inbox", "New message: ${message.content.title}")
        // Update your UI or show badge
    }

    override fun onMessageStatusChanged(messageId: String, status: InboxMessageStatus) {
        Log.d("Inbox", "Message $messageId status: $status")
    }
})

iOS#

swift
import AbrovaPush

// Fetch inbox messages
let filter = InboxFilter(
    status: .unread,       // nil for all
    category: "promotions", // optional
    limit: 20,
    offset: 0
)

AbrovaPush.shared.getInboxMessages(
    filter: filter,
    onSuccess: { response in
        print("Total: \(response.total), Unread: \(response.unreadCount)")
        for message in response.messages {
            print("Title: \(message.content.title)")
            print("Body: \(message.content.body)")
            print("Status: \(message.status.rawValue)")
        }
    },
    onError: { error in
        print("Failed to fetch: \(error)")
    }
)

// Mark message as read
AbrovaPush.shared.markInboxMessageAsRead(
    messageId: message.id,
    onSuccess: { print("Marked as read") },
    onError: { error in print("Error: \(error)") }
)

// Mark multiple messages with a status
AbrovaPush.shared.markMultipleInboxMessages(
    messageIds: ["msg_1", "msg_2", "msg_3"],
    status: .read,
    onSuccess: { print("Updated") },
    onError: { error in print("Error: \(error)") }
)

// Mark all messages as read
AbrovaPush.shared.markAllInboxMessagesAsRead(
    onSuccess: { print("All marked as read") },
    onError: { error in print("Error: \(error)") }
)

// Archive a message
AbrovaPush.shared.archiveInboxMessage(
    messageId: message.id,
    onSuccess: { print("Archived") },
    onError: { error in print("Error: \(error)") }
)

// Delete a message
AbrovaPush.shared.deleteInboxMessage(
    messageId: message.id,
    onSuccess: { print("Deleted") },
    onError: { error in print("Error: \(error)") }
)

// Get unread count (from cache, synchronous)
let unreadCount = AbrovaPush.shared.getInboxUnreadCount()
print("Unread: \(unreadCount)")

// Fetch unread count from server
AbrovaPush.shared.fetchInboxUnreadCount(
    onSuccess: { count in updateBadge(count) },
    onError: { error in print("Error: \(error)") }
)

// Get cached messages (no network call)
let cachedMessages = AbrovaPush.shared.getCachedInboxMessages()

// Set callback for new inbox messages
class MyInboxHandler: InboxCallback {
    func inboxMessageReceived(_ message: InboxMessage) {
        print("New message: \(message.content.title)")
        // Update your UI or show badge
    }

    func inboxMessageStatusChanged(messageId: String, status: InboxMessageStatus) {
        print("Message \(messageId) status: \(status.rawValue)")
    }
}

// The callback is held weakly, so keep a strong reference (e.g. a property).
let handler = MyInboxHandler()
AbrovaPush.shared.setInboxCallback(handler)

Web#

Requires @abrova/push-web 0.1.6 or newer, and a registered device.

typescript
import AbrovaPush from '@abrova/push-web';

const abrovaPush = new AbrovaPush({ apiKey: 'ab_live_your_api_key' });
await abrovaPush.register({ userId: 'user_123' });

// Live updates — an inbox message never shows a notification
abrovaPush.inbox.onMessage((message) => {
  console.log('New:', message.content.title);
});
abrovaPush.inbox.onUnreadCountChange((count) => {
  document.querySelector('.badge').textContent = String(count);
});

// Read
const { messages, total, unreadCount } = await abrovaPush.inbox.getMessages({
  status: 'unread',
  category: 'promotions',
  limit: 20,
});
const cached = abrovaPush.inbox.getCachedMessages(); // instant, no network

// Write
await abrovaPush.inbox.markAsRead(messages[0].id);
await abrovaPush.inbox.archiveMessage(messages[0].id);
await abrovaPush.inbox.deleteMessage(messages[0].id);
await abrovaPush.inbox.markMultiple(['msg_1', 'msg_2'], 'read');
await abrovaPush.inbox.markAllAsRead();

// Badge count without a fetch
const count = abrovaPush.inbox.getUnreadCount();

Messages are cached per device and restored on the next page load, so an inbox screen renders instantly and refreshes in the background. The cache is dropped when the signed-in user changes (setUserId / clearUserId).

Building an Inbox UI (Flutter)#

dart
class InboxScreen extends StatefulWidget {
  @override
  _InboxScreenState createState() => _InboxScreenState();
}

class _InboxScreenState extends State<InboxScreen> {
  List<InboxMessage> messages = [];
  bool loading = true;

  @override
  void initState() {
    super.initState();
    loadMessages();
  }

  Future<void> loadMessages() async {
    final result = await AbrovaPush.getInboxMessages();
    setState(() {
      messages = result.messages;
      loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Inbox')),
      body: loading
          ? Center(child: CircularProgressIndicator())
          : ListView.builder(
              itemCount: messages.length,
              itemBuilder: (context, index) {
                final message = messages[index];
                return ListTile(
                  leading: message.content.imageUrl != null
                      ? Image.network(message.content.imageUrl!, width: 50)
                      : Icon(Icons.mail),
                  title: Text(
                    message.content.title,
                    style: TextStyle(
                      fontWeight: message.status == InboxMessageStatus.unread
                          ? FontWeight.bold
                          : FontWeight.normal,
                    ),
                  ),
                  subtitle: Text(message.content.body, maxLines: 2),
                  trailing: message.status == InboxMessageStatus.unread
                      ? Container(
                          width: 8,
                          height: 8,
                          decoration: BoxDecoration(
                            color: Colors.blue,
                            shape: BoxShape.circle,
                          ),
                        )
                      : null,
                  onTap: () async {
                    await AbrovaPush.markInboxMessageAsRead(message.id);
                    if (message.content.deepLink != null) {
                      // Navigate to deep link
                    }
                    loadMessages();
                  },
                );
              },
            ),
    );
  }
}

Best Practices#

  • 1.Show unread badge — Display an unread count badge on your inbox icon to encourage users to check messages.
  • 2.Use categories — Organize messages into categories so users can filter and find relevant content.
  • 3.Set expiration dates — Use expiresAt for time-sensitive offers to automatically hide outdated content.
  • 4.Include deep links — Always add a deep link so users can take action directly from the message.
  • 5.Mark as read — Automatically mark messages as read when opened to keep the unread count accurate.
  • 6.Paginate results — Use limit and offset parameters when fetching messages to handle large inboxes efficiently.
این صفحه مفید بود؟
گزارش مشکل