iOS
Delivery Receipt

Delivery Receipt

Delivery receipt is a feature that indicates whether a message has successfully been delivered to all the intended recipients by Sendbird server. With the implementation of delivery receipt, Sendbird not only provides the timestamp of when each user has last read the messages in each channel, but also the timestamp of when each user has the message last delivered to each user in each channel.

DeliveryReceipt

Note: Delivery receipt is only applicable to group channels.


Benefits

Delivery receipt provides the following benefits.

Highly in demand feature

Most popular messaging apps, such as WhatsApp and Facebook Messenger, provide this feature. Users who have experienced them expect to see if the message they sent has been successfully delivered when using a new chat service. Delivery receipt is a feature in high demand that today’s users are accustomed to using.

Improved user experience

Previously, a sender had no way of knowing whether their message was unread because the server is in the process of delivering or failed to deliver the message due to the unreliable internet connection, or simply because the recipients haven’t yet read the message. This feature enables users to become better-informed, thus improving the Sendbird user experience.


How it works

Delivery receipt works in a similar way to read receipt. The server stores the timestamp of the message last delivered as delivered_ts. The timestamp is recorded per user, per channel.

SDK methods

Each SDK provides methods to make the Chat API's mark all messages as delivered action calls. If required, each SDK also makes necessary changes to handle the new response key, delivery_receipt, for the APIs that return a group channel resource.

Delivery event

When a user sends a message to a group channel by calling the SDK’s sendUserMessageWithParams with Objective-C or sendUserMessage with Swift, it is considered a delivery event for Sendbird server.


Prerequisite

To use delivery receipt, Notification Service Extension should be implemented in advance to receive the contents of your remote notifications before they are displayed to the devices of users, allowing the SDK to update the notification payload.

Requirements

To Implement Notification Service Extension to your iOS client app, create an App Group to combine your app and extension, and set it to the Chat SDK.

  • Your app is developed with iOS 10 SDK or later.
  • The remote notification is implemented and configured to display an alert.
  • The payload received from the remote notification includes the mutable-content key with the value set to 1.

Note: To enable mutable-content, go to Dashboard > Settings > Application > Notifications > Push notification services.

Implement Notification Service Extension

  1. In Xcode, go to File > New > Target....
  2. Select Notification Service Extension and click Next. NotificationServiceExtension
  3. In the Choose options for your new target window, enter the product name and choose options, then click Finish.
  4. When a pop-up appears asking you to activate the scheme, click Activate.
  5. Select the target of your app and click +Capability. ClickCapability
  6. Select App Groups and check if the App Groups section is in your project.
  7. Click the + button at the bottom to add a new app group.
  8. In the Add a new container window, enter the new container name which will be used in the Sendbird SDK.
  9. Go to your Apple Developer site > Account > Certificates, Identifiers & Profiles > Identifiers, and select your app.
  10. Click the checkbox to enable App Groups and click Edit. AddGroups
  11. Select the created app group and click Continue.

Your app is now prepared to mark a message as delivered when receiving the remote notification.

Set App Group in your app

In AppDelegate.m or AppDelegate.swift, set the created app group with the setAppGroup: method.

Objective-C
Swift
Light Color Skin
Copy
#import <SendBirdSDK/SendBirdSDK.h>
...

@interface AppDelegate ()

@end

@implementation AppDelegate

...

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...

    [SBDMain initWithApplicationId:@"APP_ID"];
    [SBDMain setAppGroup:@"APP_GROUP"];
    ...

    return YES;
}
...

@end
Light Color Skin
Copy
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    ...

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        ...

        SBDMain.initWithApplicationId("APP_ID")
        SBDMain.setAppGroup("APP_GROUP")
        ...

        return true
    }

    ...
}

Set App Group in the extension

You can find the automatically generated files for the extension in Xcode. Choose one of the following two files and set the app group with the setAppGroup: method within the corresponding method.

  • NotificationService.m > didReceiveNotificationRequest:withContentHandler:
  • NotificationService.swift > didReceive(_:withContentHandler:)
Objective-C
Swift
Light Color Skin
Copy
#import <SendBirdSDK/SendBirdSDK.h>
...

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    ...

    // Modify the notification content here
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    ...

    [SBDMain setAppGroup:@"APP_GROUP"];
    ...

    self.contentHandler(self.bestAttemptContent);
}
Light Color Skin
Copy
import SendBirdSDK

class NotificationService: UNNotificationServiceExtension {
    
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent {
            // Modify the notification content here
            bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
            ...

            SBDMain.setAppGroup("APP_GROUP")
            ...

            contentHandler(bestAttemptContent)
        }
    }
    ...

}

Mark messages as delivered

To mark messages as delivered when a group channel member successfully receives a push notification for the message from APNs, the markAsDeliveredWithRemoteNotificationPayload:completionHandler: method should be implemented in Notification Service Extension.

Objective-C
Swift
Light Color Skin
Copy
#import <SendBirdSDK/SendBirdSDK.h>
...

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    ...

    // Modify the notification content here
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    ...

    [SBDMain setAppGroup:@"APP_GROUP"];
    [SBDMain markAsDeliveredWithRemoteNotificationPayload:self.bestAttemptContentuserInfo completionHandler:^(SBDError * _Nullable error) {
        if (error != nil) { // Error.
            return;
        }
    }];
    ...

    self.contentHandler(self.bestAttemptContent);
}
Light Color Skin
Copy
import SendBirdSDK

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent {
            // Modify the notification content here
            bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
            ...

            SBDMain.setAppGroup("APP_GROUP")
            SBDMain.markAsDelivered(withRemoteNotificationPayload: bestAttemptContent.userInfo) { (error) in
                if error != nil {   // Error.
                    return
                }
            }
            ...

            contentHandler(bestAttemptContent)
        }
    }
    ...
}

Receive an update event for delivery receipt

When a message is delivered to an online group channel member, it is automatically marked as delivered and the other online members are notified of delivery receipt through the channelDidUpdateDeliveryReceipt: method of SBDChannelDelegate. However, when it is delivered to an offline member as a push notification, the message can be marked as delivered through the markAsDeliveredWithRemoteNotificiationPayload method of SBDMain, and other online members are notified of the delivery receipt through the channelDidUpdateDeliveryReceipt: method.

Objective-C
Swift
Light Color Skin
Copy
@interface GroupChannelChattingViewController : UIViewController<SBDChannelDelegate>

@end

@implementation GroupChannelChattingViewController

- (void)initGroupChannelChattingViewController {
    [SBDMain addChannelDelegate:self identifier:UNIQUE_DELEGATE_ID];
}

- (void)channelDidUpdateDeliveryReceipt:(SBDGroupChannel * _Nonnull)sender {
    ...

}

@end
Light Color Skin
Copy
class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {
    SBDMain.add(self as SBDChannelDelegate, identifier: UNIQUE_DELEGATE_ID)
    
    func channelDidUpdateDeliveryReceipt(_ sender: SBDGroupChannel) {
        ...

    }
}

Retrieve number of members who haven’t received a message

You can retrieve the number of members who haven’t received a specific message in a group channel. If zero is returned, it means that the message has been delivered to all the other members.

Objective-C
Swift
Light Color Skin
Copy
int count = [channel getUndeliveredMemberCount:message];
Light Color Skin
Copy
let count = channel?.getUndeliveredMemberCount(message)