             return [
                 "title": t.title,
                 "url": t.url.absoluteString,
+                "displayURL": t.url.absoluteDisplayString,
+                "deviceName": t.deviceName as Any,
                 ] as NSDictionary
-            } as NSArray
-        userInfo["sentTabs"] = serializedTabs
+            }
 
         userInfo["didFinish"] = didFinish
 
-        notificationContent.userInfo = userInfo
+        func present(_ tabs: [NSDictionary]) {
+            if !tabs.isEmpty {
+                userInfo["sentTabs"] = tabs as NSArray
+            }
+            notificationContent.userInfo = userInfo
+            presentNotification(tabs)
+        }
 
+        let center = UNUserNotificationCenter.current()
+        center.getDeliveredNotifications { notifications in
+
+            // Let's deal with sent-tab-notifications
+            let sentTabNotifications = notifications.filter {
+                $0.request.content.categoryIdentifier == CategorySentTab
+            }
+
+            // We can delete zero tab sent-tab-notifications
+            let emptyTabNotificationsIds = sentTabNotifications.filter {
+                $0.request.content.userInfo["sentTabs"] == nil
+                }.map { $0.request.identifier }
+            center.removeDeliveredNotifications(withIdentifiers: emptyTabNotificationsIds)
+
+            // The one we've just received (but not delivered) may not have any tabs in it either
+            // e.g. if the previous one consumed two tabs.
+            if serializedTabs.count == 0 {
+                // In that case, we try and recycle an existing notification (one that has a tab in it).
+                if let firstNonEmpty = sentTabNotifications.first(where: { $0.request.content.userInfo["sentTabs"] != nil }),
+                    let previouslyDeliveredTabs = firstNonEmpty.request.content.userInfo["sentTabs"] as? [NSDictionary] {
+                    center.removeDeliveredNotifications(withIdentifiers: [firstNonEmpty.request.identifier])
+                    return present(previouslyDeliveredTabs)
+                }
+            }
+
+            // We have tabs in this notification, or we couldn't recycle an existing one that does.
+            present(serializedTabs)
+        }
+    }
+
+    func presentNotification(_ tabs: [NSDictionary]) {
         let title: String
         let body: String
 
-        if sentTabs.isEmpty {
+        if tabs.count == 0 {
             title = Strings.SentTab_NoTabArrivingNotification_title
             body = Strings.SentTab_NoTabArrivingNotification_body
         } else {
-            let deviceNames = Set(sentTabs.flatMap { $0.deviceName })
+            let deviceNames = Set(tabs.flatMap { $0["deviceName"] as? String })
             if let deviceName = deviceNames.first, deviceNames.count == 1 {
                 title = String(format: Strings.SentTab_TabArrivingNotification_WithDevice_title, deviceName)
             } else {
                 title = Strings.SentTab_TabArrivingNotification_NoDevice_title
             }
 
-            if sentTabs.count == 1 {
-                body = sentTabs[0].url.absoluteDisplayString
+            if tabs.count == 1 {
+                // We give the fallback string as the url,
+                // because we have only just introduced "displayURL" as a key.
+                body = (tabs[0]["displayURL"] as? String) ??
+                    (tabs[0]["url"] as! String)
             } else if deviceNames.count == 0 {
                 body = Strings.SentTab_TabArrivingNotification_NoDevice_body
             } else {

    }

    func displayNotification(_ didFinish: Bool) {
        var userInfo = notificationContent.userInfo

        // Add the tabs we've found to userInfo, so that the AppDelegate 

import UserNotifications

private let log = Logger.browserLogger

private let CategorySentTab = "org.mozilla.ios.SentTab.placeholder"

class NotificationService: UNNotificationServiceExtension {
    var display: SyncDataDisplay!
