Información sobre la entrega de mensajes

FCM cuenta con tres conjuntos de herramientas para ayudarte a obtener información sobre la entrega de mensajes:

  • Informes de entrega de mensajes de Firebase console
  • Métricas agregadas de entrega del SDK de Android de la API de datos de Firebase Cloud Messaging
  • Exportación integral de datos a Google BigQuery

Todas las herramientas de informes descritas en esta página requieren Google Analytics para funcionar. Si no tienes Google Analytics habilitado en tu proyecto, puedes hacerlo en la pestaña Integraciones de la configuración del proyecto de Firebase.

Ten en cuenta que, debido a la agrupación en lotes de los datos de estadísticas, el informe de muchas de las estadísticas que se muestran en esta página está sujeto a retrasos de hasta 24 horas.

Informes sobre la entrega de mensajes

En la pestaña Informes de Firebase console, se muestran los siguientes datos de los mensajes que se envían a los SDK de FCM para la plataforma de Apple o Android, incluidos los que se envían mediante el Compositor de Notifications y las API de FCM:

  • Enviados: El mensaje de datos o de notificación se puso en cola para la entrega o se transmitió correctamente a un servicio de terceros, como los APNS, a fin de que se entregue. Consulta la duración de un mensaje para obtener más información.
  • Recibidos (solo disponible en los dispositivos Android): La app recibió el mensaje de datos o de notificación. Los datos estarán disponibles si el dispositivo Android receptor tiene instalada la versión 18.0.1 o posterior del SDK de FCM.
  • Impresiones (disponible solo para los mensajes de notificación en dispositivos Android): La notificación apareció en la pantalla del dispositivo mientras la app estaba en segundo plano.
  • Abiertos: El usuario abrió el mensaje de notificación. Solo se informa para las notificaciones que se recibieron cuando la app estaba en segundo plano.

Los datos están disponibles en todos los mensajes con carga útil de notificaciones y en todos los mensajes de datos etiquetados. Para obtener más información sobre las etiquetas, consulta Agrega etiquetas de estadísticas a los mensajes.

Cuando miras informes de mensajes, puedes definir un período para los datos que se muestran y tienes la opción de exportarlos en formato CSV. También puedes filtrar según estos criterios:

  • Plataforma (iOS o Android)
  • App
  • Etiquetas de estadísticas personalizadas

Agrega etiquetas de estadísticas a los mensajes

Etiquetar los mensajes es muy útil para los análisis personalizados, ya que te permite filtrar las estadísticas de entrega según etiquetas o conjuntos de ellas. Puedes agregar una etiqueta a cualquier mensaje enviado mediante la API de HTTP v1. Para ello, configura el campo fcmOptions.analyticsLabel del objeto message o los campos específicos de la plataforma, AndroidFcmOptions o ApnsFcmOptions.

Las etiquetas de estadísticas son strings de texto que tienen el formato ^[a-zA-Z0-9-_.~%]{1,50}$. Pueden incluir letras mayúsculas y minúsculas, números y los siguientes símbolos:

  • -
  • ~
  • %

La longitud máxima es de 50 caracteres Puedes especificar hasta 100 etiquetas únicas por día. No se informarán los mensajes con etiquetas que se agreguen después de llegar a esta cantidad.

En la pestaña Informes de mensajería de Firebase console, puedes buscar una lista de las etiquetas existentes y aplicarlas de manera individual o en conjunto a fin de filtrar las estadísticas que se muestran.

Datos de entrega agregados mediante la API de datos de FCM

Con la API de datos de Firebase Cloud Messaging, puedes recuperar información que te ayudará a comprender los resultados de las solicitudes de mensajes orientados a las apps para Android. La API proporciona datos agregados de todos los dispositivos Android con acceso a la recopilación de datos de un proyecto. Esto incluye detalles sobre el porcentaje de mensajes entregados sin retrasos, así como cuántos mensajes se retrasaron o se lanzaron en la capa de transporte de Android. La evaluación de estos datos puede revelar tendencias generales sobre la entrega de mensajes y ayudarte a encontrar formas eficaces para mejorar el rendimiento de tus solicitudes de envío. Consulta el artículo Cronogramas de datos agregados para obtener información sobre la disponibilidad del período en los informes.

La API proporciona todos los datos disponibles de una aplicación determinada. Consulta la documentación de referencia de la API.

¿Cómo se desglosan los datos?

Los datos de entrega se desglosan por aplicación, fecha y etiqueta de estadísticas. Una llamada a la API mostrará los datos de cada combinación de fecha, aplicación y etiqueta de estadísticas. Por ejemplo, un solo objeto JSON androidDeliveryData se vería de la siguiente manera:

 {
  "appId": "1:23456789:android:a93a5mb1234efe56",
  "date": {
    "year": 2021,
    "month": 1,
    "day": 1
  },
  "analyticsLabel": "foo",
  "data": {
    "countMessagesAccepted": "314159",
    "messageOutcomePercents": {
      "delivered": 71,
      "pending": 15
    },
   "deliveryPerformancePercents": {
      "deliveredNoDelay": 45,
      "delayedDeviceOffline": 11
    }
  }

Cómo interpretar las métricas

Los datos de entrega describen el porcentaje de mensajes que coincide con cada una de las siguientes métricas. Es posible que un solo mensaje coincida con varias métricas. Debido a limitaciones en la forma en que recopilamos los datos y en el nivel de detalle al que agregamos las métricas, algunos resultados de mensajes no se presentan en absoluto en las métricas, por lo que los porcentajes que aparecen a continuación no deben sumar un 100%.

Recuento de los mensajes aceptados

El único recuento incluido en el conjunto de datos es el de mensajes que FCM aceptó para entregar a dispositivos Android. Todos los porcentajes usan este valor como denominador. Ten en cuenta que el recuento no incluirá mensajes dirigidos a los usuarios que inhabilitaron la recopilación de información de uso y diagnóstico en sus dispositivos.

Porcentajes de resultados de mensajes

Los campos incluidos en el objeto MessageOutcomePercents proporcionan información sobre los resultados de las solicitudes de mensaje. Todas las categorías se excluyen mutuamente. Puede responder preguntas como “¿Los mensajes se están entregando?” o “¿Qué provoca la pérdida de mensajes?”.

Por ejemplo, un valor alto para el campo droppedTooManyPendingMessages podría indicar que las instancias de la app reciben volúmenes de mensajes no contraíbles que exceden el límite de 100 mensajes pendientes de FCM. Para mitigar este problema, asegúrate de que tu app maneje las llamadas a onDeletedMessages y considera enviar mensajes contraíbles. Del mismo modo, los porcentajes altos de droppedDeviceInactive pueden ser un indicador para actualizar los tokens de registro en tu servidor, quitar los tokens obsoletos y anular la suscripción a los temas. Consulta Administra tokens de registro de FCM para conocer las prácticas recomendadas de esta área.

Porcentajes de rendimiento de la entrega

Los campos del objeto DeliveryPerformancePercents proporcionan información sobre los mensajes que se enviaron correctamente. Puede responder preguntas como “¿Se retrasaron mis mensajes?” o “¿Por qué se retrasaron los mensajes?”. Por ejemplo, un valor alto de delayedMessageThrottled indica claramente que superas los límites máximos por dispositivo y debes ajustar la velocidad a la que envías mensajes.

Porcentajes de estadísticas de mensajes

Este objeto proporciona información adicional sobre todos los envíos de mensajes. El campo priorityLowered expresa el porcentaje de mensajes aceptados que tuvieron una prioridad menor de HIGH a NORMAL. Si este valor es alto, intenta enviar menos mensajes de prioridad alta o asegúrate de mostrar siempre una notificación cuando se envíe un mensaje con prioridad alta. Consulta nuestra documentación sobre la prioridad de los mensajes para obtener más información.

¿En qué se diferencian estos datos de los exportados a BigQuery?

La exportación a BigQuery proporciona registros de mensajes individuales sobre la aceptación de mensajes por parte del backend de FCM y la entrega de mensajes en el SDK del dispositivo (pasos 2 y 4 de la arquitectura de FCM). Estos datos son útiles para garantizar que se acepten y entreguen mensajes individuales. Obtén más información sobre la exportación de datos de BigQuery en la próxima sección.

Por el contrario, la API de datos de Firebase Cloud Messaging proporciona detalles agregados sobre lo que sucede específicamente en la capa de transporte de Android (o en el paso 3 de la arquitectura de FCM). Estos datos proporcionan estadísticas específicamente sobre la entrega de mensajes de backends de FCM al SDK de Android. Es muy útil para mostrar tendencias en cuanto a por qué se retrasaron o disminuyeron los mensajes durante este transporte.

En algunos casos, es posible que los dos conjuntos de datos no coincidan de forma precisa debido a los siguientes motivos:

  • Las métricas agregadas solo muestran una parte de todos los mensajes.
  • Las métricas agregadas se redondearon.
  • No presentamos métricas por debajo de un límite de privacidad.
  • Falta una parte de los resultados de los mensajes debido a las optimizaciones en la forma en que administramos el gran volumen de tráfico.

Limitaciones de la API

Cronogramas de datos agregados

En la API, se mostrarán 7 días de datos históricos. Sin embargo, los datos que se muestren en esta API tendrán una demora de hasta 5 días. Por ejemplo, los datos del 9 al 15 de enero estarán disponibles el 20 de enero, pero no los del 16 de enero o después. Además, los datos se proporcionan según el mejor esfuerzo. En el caso de una interrupción de los datos, FCM trabajará para solucionarla y no reabastecerá los datos después de que se solucione el problema. En interrupciones más grandes, los datos podrían no estar disponibles durante una semana o más.

Cobertura de datos

Las métricas que proporciona la API de datos de Firebase Cloud Messaging tienen como objetivo proporcionar estadísticas sobre tendencias generales de entrega de mensajes. Sin embargo, no proporcionan una cobertura del 100% de todas las situaciones de los mensajes. Las siguientes situaciones son resultados conocidos que no se reflejan en las métricas.

Mensajes contraídos

Los mensajes que otros mensajes contraen no aparecen en el conjunto de datos.

Mensajes a dispositivos inactivos

Los mensajes enviados a dispositivos inactivos pueden o no aparecer en el conjunto de datos, según la ruta de datos que se elija. Esto puede causar recuentos incorrectos en los campos droppedDeviceInactive y pending.

Mensajes a dispositivos con ciertas preferencias de usuario

En el caso de los usuarios que inhabilitaron la recopilación de información de uso y diagnóstico en sus dispositivos, no se incluirán sus mensajes en el recuento de acuerdo con sus preferencias.

Redondeo y mínimos

FCM redondea y excluye deliberadamente conteos cuando los volúmenes no son lo suficientemente grandes.

Exporta datos a BigQuery

Puedes exportar tus datos de mensajes a BigQuery para analizarlos en detalle. Esta herramienta te permite analizar los datos con BigQuery SQL, exportarlos a otro proveedor de servicios en la nube o usarlos en tus modelos de AA personalizados. La exportación a BigQuery incluye todos los datos disponibles para los mensajes, independientemente del tipo de mensaje o si este se envía a través de la API o del Compositor de Notifications.

En el caso de los mensajes enviados a dispositivos con las siguientes versiones mínimas del SDK de FCM, tienes la opción adicional de habilitar la exportación de datos de entrega de mensajes en tu app:

  • Android: 20.1.0 o versiones posteriores
  • iOS: 8.6.0 o versiones posteriores
  • SDK web de Firebase 9.0.0 o superior

Consulta los siguientes detalles a fin de habilitar la exportación de datos para iOS y Android.

Para comenzar, vincula tu proyecto a BigQuery:

  1. Elige una de las siguientes opciones:

    • Abre el Compositor de Notifications y haz clic en Acceder a BigQuery al final de la página.

    • En la página Integraciones de Firebase console, haz clic en Vincular en la tarjeta de BigQuery.

      En esta página, verás las opciones de exportación de FCM para todas las apps del proyecto que tengan FCM habilitado.

  2. Sigue las instrucciones en pantalla para habilitar BigQuery.

Consulta Cómo vincular Firebase a BigQuery para obtener más información.

Cuando habilitas la exportación de BigQuery para Cloud Messaging, sucede lo siguiente:

  • Firebase exporta tus datos a BigQuery. Ten en cuenta que la propagación inicial de los datos que se van a exportar puede demorar hasta 48 horas en completarse.

  • Después de crear el conjunto de datos, no se puede cambiar la ubicación, pero puedes copiar el conjunto de datos en una ubicación diferente o mover manualmente (volver a crear) el conjunto de datos a otra ubicación. Para obtener más información, consulta Cambia la ubicación del conjunto de datos.

  • Firebase configura sincronizaciones frecuentes de los datos del proyecto de Firebase con BigQuery. Estas operaciones de exportación diaria comienzan a las 4:00 a.m., hora del Pacífico, y suelen tardar 24 horas en finalizarse.

  • Según la configuración predeterminada, todas las apps de tu proyecto se vinculan a BigQuery, y cualquier app que agregues luego al proyecto también se vinculará automáticamente. Puedes administrar cuáles apps envían datos.

Para inhabilitar la exportación a BigQuery, desvincula el proyecto en Firebase console.

Habilita la exportación de datos de entrega de mensajes

Los dispositivos iOS con el SDK de FCM 8.6.0 o superior pueden habilitar la exportación de datos de entrega de mensajes en la app. FCM admite la exportación de datos para las alertas y las notificaciones segundo plano. Antes de habilitar estas opciones, debes crear el vínculo de FCM-BigQuery para tu proyecto como se describe en la Exportación de datos de BigQuery.

Habilita la exportación de datos de entrega para las notificaciones de alertas

Debido a que solo las notificaciones de alerta pueden activar extensiones de aplicación del servicio de notificaciones, debes agregar una extensión del servicio de notificaciones a la app y llamar a esta API dentro de una extensión de servicio para habilitar el seguimiento de mensajes en pantalla. Consulta la documentación de Apple sobre cómo modificar contenido en notificaciones entregadas recientemente.

Se debe realizar la siguiente llamada para cada notificación recibida:

Swift

// For alert notifications, call the API inside the service extension:
class NotificationService: UNNotificationServiceExtension {
  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
  Messaging.extensionHelper()
      .exportDeliveryMetricsToBigQuery(withMessageInfo:request.content.userInfo)
  }
}

Objective‑C

// For alert notifications, call the API inside the service extension:
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                   withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
  [[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:request.content.userInfo];
}
@end

Si creas solicitudes de envío mediante la API de HTTP v1, asegúrate de especificar mutable-content = 1 en el objeto de carga útil.

Habilita la exportación de datos de entrega para las notificaciones en segundo plano

En el caso de los mensajes en segundo plano recibidos cuando la app se ejecuta en primer o segundo plano, puedes llamar a la API de exportación de datos dentro del controlador de mensajes de datos de la app principal. Esta llamada se debe hacer para cada notificación recibida:

Swift

// For background notifications, call the API inside the UIApplicationDelegate or NSApplicationDelegate method:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
  Messaging.extensionHelper().exportDeliveryMetricsToBigQuery(withMessageInfo:userInfo)
}

Objective‑C

// For background notifications, call the API inside the UIApplicationDelegate or NSApplicationDelegate method:
@implementation AppDelegate
- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  [[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}
@end

¿Qué datos se exportan a BigQuery?

Ten en cuenta que la inclusión de tokens o registros inactivos puede inflar algunas de estas estadísticas.

Este es el esquema de la tabla exportada:

_PARTITIONTIME TIMESTAMP Esta pseudocolumna contiene una marca de tiempo del inicio del día (en UTC) en el que se cargaron los datos. Para la partición AAAAMMDD, contiene el valor TIMESTAMP('AAAA-MM-DD').
event_timestamp TIMESTAMP La marca de tiempo del evento que registró el servidor.
project_number NÚMERO ENTERO El número de proyecto permite identificar qué proyecto envió el mensaje.
message_id STRING El ID del mensaje permite identificar un único mensaje. Dado que se genera a partir del ID de la app y la marca de tiempo, es posible que, en algunos casos, el ID del mensaje no sea único de manera global.
instance_id STRING El ID único de la app a la que se envía el mensaje (si está disponible). Puede ser un ID de instancia o de instalación de Firebase.
message_type STRING El tipo de mensaje. Puede ser un mensaje de notificación o uno de datos. El tema se usa para identificar el mensaje original de un tema o una campaña enviados. Los mensajes posteriores son de notificación o de datos.
sdk_platform STRING La plataforma de la app receptora.
app_name STRING El nombre del paquete de las apps para Android o el ID de paquete de las apps para iOS.
collapse_key STRING La clave de contracción permite identificar un grupo de mensajes que puede contraerse. Si un dispositivo no está conectado, solo el último mensaje que tenga una clave de contracción se pondrá en cola para entregarlo en el futuro.
priority NÚMERO ENTERO Es la prioridad del mensaje. Los valores válidos son “normal” y “high”. En iOS, estos corresponden a las prioridades 5 y 10 de APNS.
ttl NÚMERO ENTERO Este parámetro especifica el tiempo (en segundos) que el mensaje se debe conservar en el almacenamiento de FCM si el dispositivo se encuentra sin conexión.
topic STRING El nombre del tema al que se envió un mensaje (si corresponde).
bulk_id NÚMERO ENTERO El ID masivo permite identificar un grupo de mensajes relacionados, como un envío particular a un tema.
event STRING Es el tipo de evento. Los valores posibles son los siguientes:
  • MESSAGE_ACCEPTED: El servidor de FCM recibió el mensaje y la solicitud es válida.
  • MESSAGE_DELIVERED: El SDK de FCM de la app recibió el mensaje en el dispositivo. De forma predeterminada, este campo no se propaga. Para habilitarlo, sigue las instrucciones que se indican en setDeliveryMetricsExportToBigQuery(boolean).
  • MISSING_REGISTRATIONS: Se rechazó la solicitud debido a que faltaba un registro.
  • UNAUTHORIZED_REGISTRATION: Se rechazó el mensaje porque el remitente no tiene permiso para hacer envíos al registro.
  • MESSAGE_RECEIVED_INTERNAL_ERROR: Se produjo un error no especificado cuando se procesaba la solicitud del mensaje.
  • MISMATCH_SENDER_ID: Se rechazó la solicitud para enviar un mensaje porque no coincidían el ID del remitente y el ID declarado en el punto de destino.
  • QUOTA_EXCEEDED: Se rechazó la solicitud para enviar un mensaje porque la cuota era insuficiente.
  • INVALID_REGISTRATION: Se rechazó la solicitud para enviar un mensaje porque el registro no era válido.
  • INVALID_PACKAGE_NAME: Se rechazó la solicitud para enviar un mensaje porque el nombre del paquete no era válido.
  • INVALID_APNS_CREDENTIAL: Se rechazó la solicitud para enviar un mensaje porque el certificado de APNS no era válido.
  • INVALID_PARAMETERS: Se rechazó la solicitud para enviar un mensaje porque los parámetros no eran válidos.
  • PAYLOAD_TOO_LARGE: Se rechazó la solicitud para enviar un mensaje porque la carga útil excedía el límite.
  • AUTHENTICATION_ERROR: Se rechazó la solicitud para enviar un mensaje debido a un error de autenticación (comprueba la clave de API que se usó para enviar el mensaje).
  • INVALID_TTL: Se rechazó la solicitud para enviar un mensaje porque el TTL no era válido.
analytics_label STRING Con la API de HTTP v1, se puede establecer la etiqueta de estadísticas cuando se envía el mensaje para marcarlo con fines estadísticos

¿Qué puedes hacer con los datos exportados?

En las siguientes secciones, se ofrecen ejemplos de consultas que puedes ejecutar en BigQuery con tus datos de FCM exportados.

Contar los mensajes que envió la app

SELECT app_name, COUNT(1)
FROM `project ID.firebase_messaging.data`
WHERE
  _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
  AND event = 'MESSAGE_ACCEPTED'
  AND message_id != ''
GROUP BY 1;

Contar las instancias de app únicas a las que se orientaron los mensajes

SELECT COUNT(DISTINCT instance_id)
FROM `project ID.firebase_messaging.data`
WHERE
  _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
  AND event = 'MESSAGE_ACCEPTED';

Contar los mensajes de notificación enviados

SELECT COUNT(1)
FROM `project ID.firebase_messaging.data`
WHERE
  _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
  AND event = 'MESSAGE_ACCEPTED'
  AND message_type = 'DISPLAY_NOTIFICATION';

Contar los mensajes de datos enviados

SELECT COUNT(1)
FROM `project ID.firebase_messaging.data`
WHERE
  _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
  AND event = 'MESSAGE_ACCEPTED'
  AND message_type = 'DATA_MESSAGE';

Contar los mensajes enviados a un tema o campaña

SELECT COUNT(1)
FROM `project ID.firebase_messaging.data`
WHERE
  _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
  AND event = 'MESSAGE_ACCEPTED'
  AND bulk_id = your bulk id AND message_id != '';

Para realizar un seguimiento de los eventos de un mensaje que se envió a un tema en particular, modifica esta consulta para reemplazar AND message_id != '' por AND message_id = <your message id>;.

Calcular la duración de la distribución de un tema o una campaña específicos

El tiempo de inicio de la distribución corresponde al momento en que se recibe la solicitud original, y el tiempo de finalización es el momento en que se crea el último mensaje individual dirigido a una sola instancia.

SELECT
  TIMESTAMP_DIFF(
    end_timestamp, start_timestamp, MILLISECOND
  ) AS fanout_duration_ms,
  end_timestamp,
  start_timestamp
FROM (
    SELECT MAX(event_timestamp) AS end_timestamp
    FROM `project ID.firebase_messaging.data`
    WHERE
      _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
      AND event = 'MESSAGE_ACCEPTED'
      AND bulk_id = your bulk id
  ) sent
  CROSS JOIN (
    SELECT event_timestamp AS start_timestamp
    FROM `project ID.firebase_messaging.data`
    WHERE
      _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
      AND event = 'MESSAGE_ACCEPTED'
      AND bulk_id = your bulk id
      AND message_type = 'TOPIC'
  ) initial_message;

Contar el porcentaje de mensajes entregados

SELECT
  messages_sent,
  messages_delivered,
  messages_delivered / messages_sent * 100 AS percent_delivered
FROM (
    SELECT COUNT(DISTINCT CONCAT(message_id, instance_id)) AS messages_sent
    FROM `project ID.firebase_messaging.data`
    WHERE
      _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
      AND event = 'MESSAGE_ACCEPTED'
  ) sent
  CROSS JOIN (
    SELECT COUNT(DISTINCT CONCAT(message_id, instance_id)) AS messages_delivered
    FROM `project ID.firebase_messaging.data`
    WHERE
      _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
      AND (event = 'MESSAGE_DELIVERED'
      AND message_id
      IN (
        SELECT message_id FROM `project ID.firebase_messaging.data`
        WHERE
          _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
          AND event = 'MESSAGE_ACCEPTED'
        GROUP BY 1
      )
  ) delivered;

Hacer un seguimiento de todos los eventos de un ID de mensaje y un ID de instancia específicos

SELECT *
FROM `project ID.firebase_messaging.data`
WHERE
    _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
    AND message_id = 'your message id'
    AND instance_id = 'your instance id'
ORDER BY event_timestamp;

Calcular la latencia de un ID de mensaje y un ID de instancia específicos

SELECT
  TIMESTAMP_DIFF(
    MAX(delivered_time), MIN(accepted_time), MILLISECOND
  ) AS latency_ms
FROM (
    SELECT event_timestamp AS accepted_time
    FROM `project ID.firebase_messaging.data`
    WHERE
      _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD')
      AND message_id = 'your message id'
      AND instance_id = 'your instance id'
      AND event = 'MESSAGE_ACCEPTED'
  ) sent
  CROSS JOIN (
    SELECT event_timestamp AS delivered_time
    FROM `project ID.firebase_messaging.data`
    WHERE
      _PARTITIONTIME = TIMESTAMP('date as YYYY-MM-DD') AND
      message_id = 'your message id' AND instance_id = 'your instance id'
      AND (event = 'MESSAGE_DELIVERED'
  ) delivered;