Comprender la entrega de mensajes

FCM proporciona tres conjuntos de herramientas para ayudarle a obtener información sobre la entrega de mensajes:

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

Todas las herramientas de informes descritas en esta página requieren Google Analytics para funcionar. Si Google Analytics no está habilitado para su proyecto, puede configurarlo en la pestaña de integraciones de la configuración de su proyecto de Firebase.

Tenga en cuenta que los informes de muchas de las estadísticas de esta página están sujetos a retrasos de hasta 24 horas debido al procesamiento por lotes de datos analíticos.

Informes de entrega de mensajes

En la pestaña Informes de Firebase console, puede ver los siguientes datos de los mensajes enviados a los SDK de FCM de la plataforma Android o Apple, incluidos los enviados a través del compositor de notificaciones y las API de FCM:

  • Envíos: el mensaje de datos o el mensaje de notificación se puso en cola para su entrega o se pasó con éxito a un servicio de terceros, como APN, para su entrega. Consulte duración de un mensaje para obtener más información.
  • Recibido (disponible solo en dispositivos Android): la aplicación recibió el mensaje de datos o el mensaje de notificación. Estos datos están disponibles cuando el dispositivo Android receptor tiene instalado FCM SDK 18.0.1 o superior.
  • Impresiones (disponible solo para mensajes de notificación en dispositivos Android): la notificación en pantalla se mostró en el dispositivo mientras la aplicación estaba en segundo plano.
  • Abre: el usuario abrió el mensaje de notificación. Se informa solo para las notificaciones recibidas cuando la aplicación está en segundo plano.

Estos datos están disponibles para todos los mensajes con una carga útil de notificación y todos los mensajes de datos etiquetados . Para obtener más información sobre las etiquetas, consulte Agregar etiquetas de análisis a los mensajes .

Al ver informes de mensajes, puede establecer un rango de fechas para los datos mostrados, con la opción de exportar a CSV. También puedes filtrar por estos criterios:

  • Plataforma (iOS o Android)
  • Aplicación
  • Etiquetas de análisis personalizadas

Agregar etiquetas de análisis a los mensajes

Etiquetar mensajes es muy útil para análisis personalizados, ya que le permite filtrar las estadísticas de entrega por etiquetas o conjuntos de etiquetas. Puede agregar una etiqueta a cualquier mensaje enviado a través de la API HTTP v1 configurando el campo fcmOptions.analyticsLabel en el objeto del mensaje o en los campos AndroidFcmOptions o ApnsFcmOptions específicos de la plataforma.

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

  • -
  • ~
  • %

La longitud máxima es de 50 caracteres. Puede especificar hasta 100 etiquetas únicas por día; los mensajes con etiquetas agregadas más allá de ese límite no se informan.

En la pestaña Informes de mensajería de Firebase console, puede buscar una lista de todas las etiquetas existentes y aplicarlas individualmente o en combinación para filtrar las estadísticas mostradas.

Datos de entrega agregados a través de la API de datos de FCM

La API de datos de Firebase Cloud Messaging te permite recuperar información que puede ayudarte a comprender los resultados de las solicitudes de mensajes dirigidas a aplicaciones de Android. La API proporciona datos agregados en todos los dispositivos Android habilitados para la recopilación de datos en un proyecto. Esto incluye detalles sobre el porcentaje de mensajes entregados sin demora, así como cuántos mensajes se retrasaron o descartaron dentro de la capa de transporte de Android . La evaluación de estos datos puede revelar tendencias generales en la entrega de mensajes y ayudarlo a encontrar formas efectivas de mejorar el rendimiento de sus solicitudes de envío. Consulte Cronogramas de datos agregados para obtener información sobre la disponibilidad del rango de fechas en los informes.

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

¿Cómo se desglosan los datos?

Los datos de entrega se desglosan por aplicación, fecha y etiqueta de análisis . Una llamada a la API devolverá datos para cada combinación de fecha, aplicación y etiqueta de análisis. Por ejemplo, un único objeto JSON androidDeliveryData tendría este aspecto:

 {
  "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 se ajustan a cada una de las siguientes métricas. Es posible que un solo mensaje se ajuste a varias métricas. Debido a las limitaciones en la forma en que recopilamos los datos y el nivel de granularidad con el que agregamos las métricas, algunos resultados de mensajes no están representados en las métricas en absoluto, por lo que los porcentajes siguientes no suman 100%.

Contar mensajes aceptados

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

Porcentajes de resultados del mensaje

Los campos incluidos en el objeto MessageOutcomePercents proporcionan información sobre los resultados de las solicitudes de mensajes. Todas las categorías son mutuamente excluyentes. Puede responder preguntas como "¿Se están entregando mis mensajes?" y "¿Qué causa que se eliminen los mensajes?"

Por ejemplo, un valor alto para el campo droppedTooManyPendingMessages podría indicar que las instancias de la aplicación están recibiendo volúmenes de mensajes no plegables que exceden el límite de FCM de 100 mensajes pendientes. Para mitigar esto, asegúrese de que su aplicación maneje llamadas a onDeletedMessages y considere enviar mensajes plegables. De manera similar, los porcentajes altos de droppedDeviceInactive podrían ser una señal para actualizar los tokens de registro en su servidor, eliminar los tokens obsoletos y cancelar su suscripción a los temas. Consulte Administrar tokens de registro de FCM para conocer las mejores prácticas en esta área.

Porcentajes de rendimiento de entrega

Los campos del objeto DeliveryPerformancePercents proporcionan información sobre los mensajes que se entregaron correctamente. Puede responder preguntas como "¿Se retrasaron mis mensajes?" y "¿Por qué se retrasan los mensajes?" Por ejemplo, un valor alto para delayedMessageThrottled indicaría claramente que está excediendo los límites máximos por dispositivo y debería ajustar la velocidad a la que envía mensajes.

Porcentajes de información sobre mensajes

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

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

La exportación de 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 los mensajes individuales sean aceptados y entregados. Lea más sobre la exportación de datos de BigQuery en la siguiente 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 el paso 3 de la arquitectura FCM ). Estos datos proporcionan específicamente información sobre la entrega de mensajes desde los backends de FCM al SDK de Android. Es particularmente útil para mostrar tendencias sobre por qué los mensajes se retrasaron o descartaron durante este transporte.

En algunos casos, es posible que los dos conjuntos de datos no coincidan precisamente debido a lo siguiente:

  • Las métricas agregadas solo muestran una parte de todos los mensajes.
  • Las métricas agregadas están redondeadas.
  • No presentamos métricas por debajo de un umbral de privacidad.
  • Falta una parte de los resultados de los mensajes debido a las optimizaciones en la forma en que gestionamos el gran volumen de tráfico.

Limitaciones de la API

Cronogramas de datos agregados

La API devolverá 7 días de datos históricos; sin embargo, los datos devueltos por esta API se retrasarán hasta 5 días. Por ejemplo, el 20 de enero, los datos del 9 al 15 de enero estarían disponibles, pero no los del 16 de enero o posteriores. Además, los datos se proporcionan con el mejor esfuerzo. En caso de una interrupción de datos, FCM trabajará para solucionarlo y no repondrá los datos una vez solucionado el problema. En cortes más grandes, los datos podrían no estar disponibles durante una semana o más.

Cobertura de datos

Las métricas proporcionadas por la API de datos de Firebase Cloud Messaging están destinadas a brindar información sobre las tendencias generales de entrega de mensajes. Sin embargo, no brindan una cobertura del 100% de todos los escenarios de mensajes. Los siguientes escenarios son resultados conocidos que no se reflejan en las métricas.

Mensajes colapsados

Los mensajes que han sido contraídos por otro mensaje no aparecen en el conjunto de datos.

Mensajes a dispositivos inactivos

Los mensajes enviados a dispositivos inactivos pueden aparecer o no en el conjunto de datos según la ruta de datos que tomen. Esto puede provocar algunos errores de conteo en los campos droppedDeviceInactive y pending .

Mensajes a dispositivos con determinadas preferencias de usuario

Los usuarios que hayan desactivado la recopilación de información de uso y diagnóstico en sus dispositivos no incluirán sus mensajes en nuestro conteo, de acuerdo con sus preferencias.

Redondeo y mínimos

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

Exportación de datos de BigQuery

Puede exportar los datos de su mensaje a BigQuery para su posterior análisis. BigQuery le permite analizar los datos usando BigQuery SQL, exportarlos a otro proveedor de nube o usar los datos para sus modelos de ML personalizados. Una exportación a BigQuery incluye todos los datos disponibles para los mensajes, independientemente del tipo de mensaje o de si el mensaje se envía a través de la API o del redactor de notificaciones.

Para los mensajes enviados a dispositivos con las siguientes versiones mínimas del SDK de FCM, tiene la opción adicional de habilitar la exportación de datos de entrega de mensajes para su aplicación:

  • Android 20.1.0 o superior.
  • iOS 8.6.0 o superior
  • SDK web de Firebase 9.0.0 o superior

Consulte a continuación para obtener detalles sobre cómo habilitar la exportación de datos para Android e iOS .

Para comenzar, vincula tu proyecto a BigQuery:

  1. Elige una de las siguientes opciones:

    • Abra el redactor de notificaciones y luego haga clic en Acceder a BigQuery en la parte inferior de la página.

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

      Esta página muestra las opciones de exportación de FCM para todas las aplicaciones habilitadas para FCM en el proyecto.

  2. Siga las instrucciones que aparecen en pantalla para habilitar BigQuery.

Consulta Vincular Firebase a BigQuery para obtener más información.

Cuando habilitas la exportación de BigQuery para Cloud Messaging:

  • Firebase exporta tus datos a BigQuery. Tenga en cuenta que la propagación inicial de datos para exportación puede tardar hasta 48 horas en completarse.

  • Una vez creado el conjunto de datos, la ubicación no se puede cambiar, pero puede copiar el conjunto de datos a una ubicación diferente o mover (recrear) manualmente el conjunto de datos en una ubicación diferente. Para obtener más información, consulte Cambiar la ubicación del conjunto de datos .

  • Firebase configura sincronizaciones periódicas de tus datos desde tu proyecto de Firebase con BigQuery. Estas operaciones de exportación diarias comienzan a las 4:00 a. m., hora del Pacífico, y generalmente finalizan en 24 horas.

  • De forma predeterminada, todas las aplicaciones de tu proyecto están vinculadas a BigQuery y cualquier aplicación que agregues posteriormente al proyecto se vincula automáticamente a BigQuery. Puedes administrar qué aplicaciones envían datos .

Para desactivar la exportación de BigQuery, desvincula tu proyecto en Firebase console.

Habilitar la exportación de datos de entrega de mensajes

Los dispositivos iOS con FCM SDK 8.6.0 o superior pueden habilitar la exportación de datos de entrega de mensajes de su aplicación. FCM admite la exportación de datos tanto para alertas como para notificaciones en segundo plano. Antes de habilitar estas opciones, primero debes crear el enlace FCM-BiqQuery para tu proyecto como se describe en Exportación de datos de BigQuery .

Habilite la exportación de datos de entrega para notificaciones de alerta

Debido a que solo las notificaciones de alerta pueden activar extensiones de aplicación de servicio de notificación, debe agregar una extensión de servicio de notificación a su aplicación y llamar a esta API dentro de una extensión de servicio para habilitar el seguimiento de mensajes en pantalla. Consulte la documentación de Apple sobre cómo modificar contenido en notificaciones recién entregadas .

Por cada notificación recibida se deberá realizar la siguiente convocatoria:

Rápido

// 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)
  }
}

C objetivo

// 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 está creando solicitudes de envío utilizando la API HTTP v1, asegúrese de especificar mutable-content = 1 en el objeto de carga útil .

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

Para los mensajes en segundo plano recibidos cuando la aplicación está en primer plano o en segundo plano, puede llamar a la API de exportación de datos dentro del controlador de mensajes de datos de la aplicación principal. Esta convocatoria deberá realizarse por cada notificación recibida:

Rápido

// 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)
}

C objetivo

// 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?

Tenga en cuenta que apuntar a tokens obsoletos o registros inactivos puede inflar algunas de estas estadísticas.

El esquema de la tabla exportada es:

_PARTICIÓNTIEMPO MARCA DE TIEMPO Esta pseudocolumna contiene una marca de tiempo para el inicio del día (en UTC) en el que se cargaron los datos. Para la partición AAAAMMDD, esta pseudocolumna contiene el valor TIMESTAMP('AAAA-MM-DD').
marca_hora_evento MARCA DE TIEMPO Marca de tiempo del evento registrada por el servidor
número de proyecto ENTERO El número de proyecto identifica el proyecto que envió el mensaje.
id_mensaje CADENA El ID del mensaje identifica un mensaje. Generado a partir del ID de la aplicación y la marca de tiempo, el ID del mensaje puede, en algunos casos, no ser globalmente único.
id_instancia CADENA La identificación única de la aplicación a la que se envía el mensaje (cuando esté disponible). Puede ser un ID de instancia o un ID de instalación de Firebase.
Tipo de mensaje CADENA El tipo de mensaje. Puede ser un mensaje de notificación o un mensaje de datos. El tema se utiliza para identificar el mensaje original de un tema o envío de campaña; los mensajes posteriores son una notificación o un mensaje de datos.
sdk_plataforma CADENA La plataforma de la aplicación del destinatario.
nombre de la aplicación CADENA El nombre del paquete para aplicaciones de Android o el ID del paquete para aplicaciones de iOS
colapso_clave CADENA La clave para contraer identifica un grupo de mensajes que se pueden contraer. Cuando un dispositivo no está conectado, solo el último mensaje con una clave de colapso determinada se pone en cola para su eventual entrega.
prioridad ENTERO La prioridad del mensaje. Los valores válidos son "normal" y "alto". En iOS, estos corresponden a las prioridades 5 y 10 de APN.
ttl ENTERO Este parámetro especifica cuánto tiempo (en segundos) debe mantenerse el mensaje en el almacenamiento FCM si el dispositivo está fuera de línea.
tema CADENA El nombre del tema al que se envió un mensaje (cuando corresponda)
id_a granel ENTERO El ID masivo identifica un grupo de mensajes relacionados, como un envío particular a un tema.
evento CADENA El tipo de evento. Los valores posibles son:
  • MESSAGE_ACCEPTED: el mensaje fue recibido por el servidor FCM y la solicitud es válida;
  • MESSAGE_DELIVERED: el mensaje se entregó al SDK de FCM de la aplicación en el dispositivo. De forma predeterminada, este campo no se propaga. Para habilitarlo, siga las instrucciones proporcionadas en setDeliveryMetricsExportToBigQuery(boolean) .
  • MISSING_REGISTRACIONES: la solicitud fue rechazada debido a que falta un registro;
  • UNAUTHORIZED_REGISTRATION: el mensaje fue rechazado porque el remitente no está autorizado a enviar al registro;
  • MESSAGE_RECEIVED_INTERNAL_ERROR: hubo un error no especificado al procesar la solicitud de mensaje;
  • MISMATCH_SENDER_ID: la solicitud para enviar un mensaje fue rechazada debido a una discrepancia entre la identificación del remitente que envía el mensaje y la declarada para el punto final;
  • QUOTA_EXCEEDED: la solicitud para enviar un mensaje fue rechazada por cuota insuficiente;
  • INVALID_REGISTRATION: la solicitud para enviar un mensaje fue rechazada debido a un registro no válido;
  • INVALID_PACKAGE_NAME: la solicitud para enviar un mensaje fue rechazada debido a un nombre de paquete no válido;
  • INVALID_APNS_CREDENTIAL: la solicitud para enviar un mensaje fue rechazada debido a un certificado APNS no válido;
  • INVALID_PARAMETERS: la solicitud para enviar un mensaje fue rechazada debido a parámetros no válidos;
  • PAYLOAD_TOO_LARGE: la solicitud para enviar un mensaje fue rechazada debido a una carga útil mayor que el límite;
  • AUTHENTICATION_ERROR: la solicitud para enviar un mensaje fue rechazada debido a un error de autenticación (verifique la clave API utilizada para enviar el mensaje);
  • INVALID_TTL: la solicitud para enviar un mensaje fue rechazada debido a un TTL no válido.
etiqueta_analítica CADENA Con la API HTTP v1 , la etiqueta de análisis se puede configurar al enviar el mensaje, para marcar el mensaje con fines analíticos.

¿Qué puedes hacer con los datos exportados?

Las siguientes secciones ofrecen ejemplos de consultas que puedes ejecutar en BigQuery con tus datos FCM exportados.

Contar mensajes enviados por aplicación

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;

Cuente instancias de aplicaciones únicas a las que se dirigen 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 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 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 enviado a un tema en particular, modifique esta consulta para reemplazar AND message_id != '' con AND message_id = <your message id>; .

Calcular la duración del fanout para un tema o campaña determinados

La hora de inicio del despliegue es cuando se recibe la solicitud original y la hora de finalización es la hora 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;

Realice un seguimiento de todos los eventos para una identificación de mensaje y una identificación de instancia determinadas

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 para una identificación de mensaje y una identificación de instancia determinadas

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;