Python · Firebase · Email · Backend

Python y Firebase Admin SDK: backend Firestore con envío de emails

En este proyecto implementamos una aplicación en Python capaz de comunicarse con Firebase, descargar información almacenada en la base de datos y enviar correos electrónicos de forma automática.

El objetivo práctico será recuperar los datos de una colección y utilizarlos para enviar avisos por correo a los usuarios almacenados en Firebase.

Vídeo complementario

Si quieres ampliar el contenido del artículo, puedes consultar el siguiente vídeo:

1. Introducción

Firebase es una plataforma para el desarrollo de aplicaciones web y móviles lanzada en 2011 y adquirida por Google en 2014.

Se trata de una plataforma en la nube, integrada con Google Cloud Platform, que proporciona herramientas para crear, sincronizar y escalar proyectos sin necesidad de desarrollar toda la infraestructura desde cero.

Entre sus principales ventajas destacan las siguientes:

  • Sincronización sencilla de datos sin lógica compleja de comunicaciones.
  • Compatibilidad con aplicaciones web, Android, iOS, Unity y C++.
  • Escalado automático sobre la infraestructura de Google.
  • Posibilidad de crear proyectos sin servidor propio.
  • Amplia documentación oficial y comunidad activa.

2. Implementación

Este ejemplo se plantea desde el lado servidor. La cuenta de servicio de Firebase se carga desde Python, por lo que las credenciales privadas no llegan al navegador y el cliente no necesita permisos directos de lectura sobre la base de datos. Esa separación es importante si después los datos alimentan una app Angular, una tarea programada o un panel interno.

La recomendación actual de Firebase sigue encajando con este ejemplo, pero con un matiz de producción: si el script corre en infraestructura de Google como Cloud Run, App Engine, Firebase App Hosting o Cloud Functions, es mejor usar Application Default Credentials en lugar de una clave JSON descargada. Conserva el fichero JSON para aprendizaje local o servidores externos donde sea realmente necesario, y guardalo siempre como secreto.

En este ejemplo, el objetivo será enviar un email a las direcciones almacenadas en Firebase. Para ello, se ha definido una colección de subastas con una estructura similar a la siguiente:

{
  "FechaLimite": "01-01-2020",
  "Acceso_subasta": ["1938web@gmail.com"],
  "Activa": true,
  "Categoria": "Subasta Arte",
  "Descripcion": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..."
}

El primer paso consiste en instalar la librería firebase-admin:

pip install firebase-admin

A partir de este momento ya se podrá acceder al contenido almacenado en la base de datos. Esta información se recupera a partir de documentos JSON mediante el siguiente código:

class Subasta(SecuredResource):
    def get(self, subasta):
        try:
            import os
            import firebase_admin
            from firebase_admin import credentials
            from firebase_admin import firestore

            filename = os.path.join('clave', 'clave.json')
            cred = credentials.Certificate(filename)

            try:
                firebase_admin.initialize_app(cred)
                backend_cnx = firestore.client()
            except:
                backend_cnx = firestore.client()

            db = backend_cnx
            subasta = 'WpZxR21fgLOtUOHeIt2b'
            doc_ref = db.collection(u'subastas').document(subasta)
            doc = doc_ref.get()

            enviar_email = SendEMail()
            for i in doc.to_dict()['acceso_subasta']:
                enviar_email.send_email_puja_nueva(i, doc.to_dict())
        except:
            print('Error conexion')


class SendEMail:
    def send_email_puja_nueva(self, para, subasta):
        import smtplib
        import datetime

        gmail_user = '1938web@gmail.com'
        gmail_password = ''

        from_address = gmail_user
        to_address = para
        today = datetime.date.today().strftime('%Y-%m-%d')
        asunto = "Subasta: " + subasta['titulo'][0] + " tiene una nueva puja."
        mensaje = (
            "La subasta " + subasta['titulo'][0] +
            " tiene una nueva puja. Actualmente, la puja más alta tiene un valor de " +
            subasta['puja_ganadora'][0]
        )

        message = """From: %s\nTo: %s\nSubject: %s\n\n%s
        """ % (from_address, ", ".join(to_address), asunto, mensaje)

        server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
        server.ehlo()
        server.login(gmail_user, gmail_password)
        server.sendmail(from_address, to_address, message)
        server.close()

Si analizamos el código, uno de los puntos clave es la obtención de las credenciales JSON a partir del fichero clave.json:

import os
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore

filename = os.path.join('clave', 'clave.json')
cred = credentials.Certificate(filename)

try:
    firebase_admin.initialize_app(cred)
    backend_cnx = firestore.client()
except:
    backend_cnx = firestore.client()

Para obtener este documento, puedes acceder a la consola de Google Cloud en el siguiente enlace: https://console.cloud.google.com/projectselector/apis/api/firebasedatabase.googleapis.com/overview?hl=es

Una vez configurado el acceso, ya es posible recuperar directamente la información de la base de datos:

db = backend_cnx
subasta = 'WpZxR21fgLOtUOHeIt2b'
doc_ref = db.collection(u'subastas').document(subasta)
doc = doc_ref.get()

enviar_email = SendEMail()
for i in doc.to_dict()['acceso_subasta']:
    enviar_email.send_email_puja_nueva(i, doc.to_dict())

En resumen, Firebase permite construir soluciones backend ligeras y muy rápidas de integrar, especialmente útiles cuando queremos centralizar datos y automatizar acciones como el envío de correos.

Notas de seguridad y producción

La regla principal es que el fichero JSON de la cuenta de servicio no debe exponerse nunca en Angular ni subirse a un repositorio publico. Debe vivir en backend, protegido mediante variables de entorno, un gestor de secretos o un directorio privado de despliegue. El frontend tiene que llamar a un endpoint controlado, no conectarse con permisos administrativos.

También conviene separar la lectura de Firestore de la lógica de notificaciones. Un servicio pequeño puede leer la colección, validar los campos esperados, construir un DTO normalizado y pasar solo los valores necesarios al módulo de email. Así los errores se prueban mejor y se evita enviar mensajes a partir de documentos incompletos.

  • Usa credenciales con privilegios minimos y rota claves de servicio cuando cambie el equipo.
  • Prefiere autenticación sin claves o Application Default Credentials en runtimes gestionados de Google Cloud.
  • Registra fallos de entrega sin almacenar contenido privado del mensaje de forma innecesaria.
  • Valida direcciones de destinatario antes de llamar a SMTP o a un proveedor transaccional.
  • Mueve el proceso a una cola o tarea programada si deja de ser un script pequeño.

Un detalle del código didáctico original es deliberadamente simple: captura errores de inicialización y después intenta usar firestore.client(). En un backend real, registra la excepción exacta, distingue entre "ya inicializado" y "credenciales fallidas", y falla pronto si la cuenta de servicio no puede cargarse. Los fallos silenciosos hacen que las tareas de notificación sean muy difíciles de diagnosticar.

Otra mejora practica es añadir un modo dry-run. En ese modo el backend lee Firestore, prepara el mensaje y escribe en logs los destinatarios previstos, pero no envia el correo. Es útil para probar una estructura nueva de colección o validar que una notificación programada no contactara con usuarios equivocados.

Para SEO y mantenimiento, esta página separa la explicación conceptual de Firebase de las decisiones de seguridad del backend. Así deja de ser solo un fragmento de código y ayuda a entender por que el SDK administrativo debe vivir en Python y no en el navegador.

Si el siguiente paso es exponer esos datos a una aplicación Angular, conviene revisar el ejemplo de consumo de APIs con HttpClient y la guía para proteger una API Angular con Nginx y FastAPI.