Skip to main content

Official Python SDK for DoJa Chatbots and WhatsApp Business Integration

Project description

DoJa Python SDK 🚀

El DoJa SDK es la biblioteca oficial de Python para conectar, automatizar y escalar envíos masivos o transaccionales a través de WhatsApp Cloud API (Meta) y el Gateway de Emails DoJa.

Con DoJa SDK, te olvidas de integraciones complejas. Usa la misma licencia para disparar notificaciones en ambos canales mediante un código elegante, rápido y sin dependencias pesadas.


💻 Instalación

Solo requieres Python 3.7+ y pip. El SDK no empuja dependencias pesadas ni requiere que instales las librerías oficiales de Meta o proveedores externos de Email:

pip install doja-sdk

📚 Índice

  1. WhatsApp (Mensajería, Templates, Multimedia y Recepción)
  2. Email (Texto, HTML y Adjuntos)
  3. Plantillas de WhatsApp (Administración / CRUD)
  4. Consulta de Cuotas Restantes 📊
  5. Módulo Asíncrono (AsyncIO) ⚡
  6. Manejo de Errores y Excepciones
  7. Optimización y Desempeño

1. Módulo de WhatsApp 💬

Para interactuar con WhatsApp, necesitas tu Token de Licencia DoJa, tu Token de Meta y tu Phone ID.

Configuración Inicial

from doja_sdk import DojaClient, DojaAuthError

try:
    wa = DojaClient(
        doja_token="TU-LICENCIA-DOJA",   # Token de tu suscripción en DoJa
        whatsapp_token="EAAB12345...",    # Token Permanente/Temporal de Meta
        phone_id="100747123456"           # Identificador de tu número en Meta
    )
    print("¡Conexión a WhatsApp exitosa!")
except DojaAuthError as e:
    print(f"Error de licencia: {e}")

Ejemplos de Envío (WhatsApp)

(Nota: El destinatario debe incluir código de país sin el signo '+'. Ej: "525512345678")

Mensaje de Texto Simple:

wa.send_text("525512345678", "¡Hola! Hemos recibido tu pago exitosamente.")

Documentos (PDF, Facturas, Excel):

wa.send_document(
    to="525512345678", 
    url="https://ejemplo.com/factura.pdf", 
    caption="Aquí tienes tu factura del mes 📝", 
    filename="Factura_Octubre.pdf" # Opcional: Nombre con el que se descarga
)

Imágenes y Promocionales:

wa.send_image(
    to="525512345678", 
    url="https://ejemplo.com/promo.jpg", 
    caption="¡Aprovecha nuestro descuento de temporada!"
)

Ubicación Compartida (Abre interactivo en Google Maps/Waze):

wa.send_location(
    to="525512345678", 
    latitude=19.432608, 
    longitude=-99.133209, 
    name="Sucursal Centro", 
    address="Centro Histórico, CDMX, México"
)

Botones Interactivos (Máximo 3 botones):

botones = [
    {"id": "soporte", "title": "Hablar con Soporte"},
    {"id": "ventas", "title": "Cotizar Servicios"}
]

wa.send_interactive_button(
    to="525512345678", 
    body_text="¿En qué área podemos apoyarte hoy?", 
    buttons_list=botones
)

Listas Desplegables (Ideal para menús largos):

secciones = [
    {
        "title": "Áreas de Atención",
        "rows": [
            {"id": "1", "title": "Soporte", "description": "Fallas técnicas"},
            {"id": "2", "title": "Ventas", "description": "Nuevas suscripciones"}
        ]
    }
]

wa.send_interactive_list(
    to="525512345678", 
    body_text="Por favor selecciona el área deseada:", 
    button_text="Ver Opciones", 
    sections=secciones
)

Templates Aprobados por Meta: (Requerido para iniciar conversación fuera de la ventana de atención de 24 hrs)

Variables POSICIONALES ({{1}}, {{2}}, ...):

wa.send_template(
    to="525512345678",
    template_name="confirmacion_pedido",  # Nombre idéntico al de Meta
    language_code="es",
    body_variables=["Juan", "#123456", "Mañana a las 15:00 hrs"]
)

Variables NOMBRADAS ({{nombre_cliente}}, {{folio}}, ...):

wa.send_template(
    to="525512345678",
    template_name="confirmacion_pedido",
    language_code="es",
    body_variables_named={
        "nombre_cliente": "Juan",
        "folio": "#123456",
        "fecha_entrega": "Mañana a las 15:00 hrs",
    }
)

[!NOTE] body_variables y body_variables_named son mutuamente excluyentes. Una plantilla de WhatsApp usa o bien placeholders posicionales ({{1}}) o bien nombrados ({{nombre}}), nunca ambos en el mismo body. Si pasas los dos, el SDK lanza DojaValidationError.

Descarga de Media Entrante (Webhooks) 📥

Cuando un cliente final te envía una imagen, documento, audio, video o sticker desde WhatsApp, el webhook de Meta no entrega el archivo: solo entrega un media_id. Si guardas ese media_id directo en tu CRM, los archivos "no se visualizan" — porque no son una URL pública, son solo un identificador.

download_media resuelve ese problema en una sola llamada:

  1. Convierte el media_id en la URL temporal que expone Meta (vida ~5 min, requiere bearer token).
  2. Descarga el binario con el mismo token.
  3. Devuelve URL + metadata (mime_type, sha256, file_size) + content (bytes), listo para subir a tu S3/CDN o reenviar al CRM.

Esta llamada NO consume crédito de tu licencia DoJa. Es media entrante, no es un mensaje enviado por tu cuenta.

Ejemplo: imagen entrante recibida en tu webhook

# En tu handler del webhook entrante de WhatsApp:
mensaje = payload["entry"][0]["changes"][0]["value"]["messages"][0]

if mensaje.get("type") == "image":
    media_id = mensaje["image"]["id"]

    media = wa.download_media(media_id)

    print(media["mime_type"])   # "image/jpeg"
    print(media["file_size"])   # 248531
    print(media["sha256"])      # hash que Meta entrega

    # Guarda el binario en TU almacenamiento (S3, GCS, R2, disco)
    with open(f"/uploads/{media_id}.jpg", "wb") as f:
        f.write(media["content"])

Mismo método para documentos, audio, video y stickers — solo cambia la clave del payload (document, audio, video, sticker):

if mensaje.get("type") == "document":
    media = wa.download_media(mensaje["document"]["id"])
    nombre_original = mensaje["document"].get("filename", "archivo.bin")
    # media["mime_type"] -> "application/pdf", "application/vnd.openxmlformats..."

if mensaje.get("type") == "audio":
    media = wa.download_media(mensaje["audio"]["id"])
    # media["mime_type"] -> "audio/ogg" (notas de voz), "audio/mpeg", ...

Solo URL temporal (sin bajar el binario) — útil si quieres hacer streaming o descargar tú mismo:

media = wa.download_media(media_id, include_bytes=False)
# media["url"]: URL de Meta. Descárgala con el header
# Authorization: Bearer <whatsapp_token>

⚠️ No guardes media["url"] directo en tu CRM. Es una URL temporal de Meta que expira en ~5 minutos y requiere el token de WhatsApp para autenticarse. Para que tu CRM pueda mostrar la imagen días o meses después, sube media["content"] a tu propio almacenamiento (S3, Cloudflare R2, GCS, disco local) y guarda la URL pública resultante en el CRM.

Forma del dict retornado:

{
    "id": "1234567890",
    "url": "https://lookaside.fbsbx.com/whatsapp_business/...",
    "mime_type": "image/jpeg",
    "sha256": "a3b1c2d4...",
    "file_size": 248531,
    "messaging_product": "whatsapp",
    "content": b"\xff\xd8\xff\xe0..."   # bytes — solo si include_bytes=True
}

Errores típicos:

  • media_id vacío → DojaValidationError.
  • media_id que ya expiró o no existe → DojaAPIError (404).
  • Token de WhatsApp inválido → DojaAPIError (401).

2. Módulo de Email 📧

El Módulo de Email está diseñado para ser extremadamente intuitivo. Para autenticarte, no requieres doble token. Tu clave de licencia de DoJa funciona automáticamente como la llave para despachar los correos mediante nuestro proveedor subyacente (Resend).

Configuracion Rapida - Email

from doja_sdk import DojaEmailClient

try:
    email = DojaEmailClient(
        doja_token="TU-LICENCIA-DOJA",           # La licencia que DoJa valida
        external_id="LA_LLAVE_DE_SERVICIO",      # ID proporcionado por el integrador
        from_address="no-reply@tuempresa.com"    # Remitente verificado
    )
    print("¡Conexión de Email exitosa!")
except DojaAuthError as e:
    print(f"Error de licencia o permisos: {e}")

Ejemplos de Envío (Email)

Texto Plano Rápido:

email.send(
    to="cliente@empresa.com",
    subject="Confirmación de Registro",
    body="Gracias por registrarte en nuestra plataforma."
)

Email con HTML enriquecido:

email.send(
    to="cliente@empresa.com",
    subject="Bienvenido a tu suscripción Pro",
    body="<h1>¡Hola Juan!</h1><p>Tu cuenta ya está activa. <a href='https://app.doja.com'>Ingresa aquí</a>.</p>",
    html=True
)

Email con Archivos Adjuntos (Rutas Locales o URLs): (El SDK procesa archivos locales codificándolos en Base64 o transfiere la URL de forma directa al Gateway)

email.send(
    to="cliente@empresa.com",
    subject="Tu Factura de Marzo",
    body="Adjuntamos tu factura digitalizada correspondiente a este mes.",
    attachments=[
        "/ruta/absoluta/factura_marzo.pdf",         # Archivo local
        "https://ejemplo.com/cotizacion_final.xlsx" # Archivo por URL
    ]
)

Funciones Avanzadas (Múltiples destinatarios, CC, BCC y Reply-To):

email.send(
    to="gerente@empresa.com, asistente@empresa.com",   # Puedes separar por comas
    subject="Reporte Financiero Semanal",
    body="<h2>Reporte Generado</h2><p>Aquí tienes el archivo de esta semana.</p>",
    html=True,
    cc=["director@empresa.com"],
    bcc=["auditoria@empresa.com"],
    reply_to="soporte@empresa.com",
    attachments=["/ruta/reporte.xlsx", "/ruta/resumen.pdf"]
)

3. Plantillas de WhatsApp (Administración) 🧩

Si necesitas registrar, listar, actualizar o eliminar plantillas (templates) en tu WhatsApp Business Account directamente desde código, usa DojaTemplateClient. A diferencia del envío, aquí necesitas tu WABA ID (WhatsApp Business Account ID), no el phone_id. Estas operaciones no consumen créditos de tu licencia DoJa.

⚠️ Las plantillas creadas entran en estado PENDING hasta que Meta las apruebe. Solo cuando estén APPROVED podrás enviarlas con wa.send_template(...).

Configuración Inicial

from doja_sdk import DojaTemplateClient

tpl = DojaTemplateClient(
    doja_token="TU-LICENCIA-DOJA",
    whatsapp_token="EAAB12345...",     # Mismo token de Meta
    waba_id="123456789012345"          # ID de la WhatsApp Business Account
)

Listar plantillas

res = tpl.list_templates(limit=50)
for t in res.get("data", []):
    print(t["name"], "—", t["status"], "—", t["category"])

# Filtrar por nombre o estado
tpl.list_templates(name="confirmacion_pedido")
tpl.list_templates(status="APPROVED")

Crear plantilla

tpl.create_template(
    name="confirmacion_pedido",
    language="es_MX",
    category="UTILITY",   # AUTHENTICATION | MARKETING | UTILITY
    components=[
        {
            "type": "HEADER",
            "format": "TEXT",
            "text": "Pedido confirmado"
        },
        {
            "type": "BODY",
            "text": "Hola {{1}}, tu pedido {{2}} está listo para envío el {{3}}.",
            "example": {"body_text": [["Juan", "#12345", "mañana"]]}
        },
        {
            "type": "FOOTER",
            "text": "Gracias por tu compra"
        },
        {
            "type": "BUTTONS",
            "buttons": [
                {"type": "QUICK_REPLY", "text": "Ver pedido"},
                {"type": "QUICK_REPLY", "text": "Hablar con soporte"}
            ]
        }
    ]
)
# → {"id": "1234567890", "status": "PENDING", "category": "UTILITY"}

Actualizar plantilla

tpl.update_template(
    template_id="1234567890",
    components=[
        {"type": "BODY",
         "text": "Hola {{1}}, tu pedido {{2}} fue actualizado.",
         "example": {"body_text": [["Juan", "#12345"]]}}
    ]
)

Eliminar plantilla

# Por nombre (elimina todas las versiones / idiomas con ese nombre)
tpl.delete_template(name="confirmacion_pedido")

# Por versión específica
tpl.delete_template(name="confirmacion_pedido", template_id="1234567890")

Versión Asíncrona

import asyncio
from doja_sdk import AsyncDojaTemplateClient

async def main():
    tpl = AsyncDojaTemplateClient(
        doja_token="TU-LICENCIA-DOJA",
        whatsapp_token="EAAB...",
        waba_id="123456789012345"
    )
    plantillas = await tpl.list_templates(limit=20)
    print(plantillas)

asyncio.run(main())

4. Consulta de Cuotas Restantes 📊

¿Quieres saber cuántos mensajes de WhatsApp y correos te quedan con tu plan? Usa la función get_remaining_quotas. Solo necesitas tu doja_token — no requiere instanciar un cliente completo y no consume crédito.

Standalone (recomendado para chequeos rápidos)

from doja_sdk import get_remaining_quotas

q = get_remaining_quotas("TU-LICENCIA-DOJA")

print(f"Mensajes restantes: {q['messages']['remaining']}/{q['messages']['included']}")
print(f"Correos restantes:  {q['emails']['remaining']}/{q['emails']['included']}")
print(f"Periodo activo:     {q['period']['start']}{q['period']['end']}")
print(f"Puede enviar:       {q['can_send']}")

Versión Asíncrona

import asyncio
from doja_sdk import get_remaining_quotas_async

async def main():
    q = await get_remaining_quotas_async("TU-LICENCIA-DOJA")
    print(q["messages"]["remaining"], "mensajes")
    print(q["emails"]["remaining"], "correos")

asyncio.run(main())

Desde un cliente ya instanciado

Cualquier cliente (DojaClient, DojaEmailClient, DojaTemplateClient, etc.) expone los contadores como atributos y un método para refrescarlos:

from doja_sdk import DojaClient

wa = DojaClient(doja_token="...", whatsapp_token="...", phone_id="...")

# Atributos disponibles tras instanciar:
print(wa.remaining_messages, wa.remaining_emails)
print(wa.used_messages, wa.used_emails)
print(wa.current_period_end)

# Refrescar (golpea el core):
quotas = wa.get_remaining_quotas()

# Usar caché de 5 min para evitar latencia:
quotas = wa.get_remaining_quotas(use_cache=True)

Forma del dict retornado

{
    "valid": True,
    "can_send": True,
    "account_id": "cm_demo_account",
    "account_name": "DoJa Workspace",
    "plan_tier": "ENTERPRISE",
    "subscription_status": "ACTIVE",
    "messages": {"included": 500000, "used": 120, "remaining": 499880},
    "emails":   {"included":  50000, "used":  24, "remaining":  49976},
    "period":   {"start": "2026-05-26T10:00:00.000Z",
                 "end":   "2026-06-26T10:00:00.000Z"},
    "channels": {"allowed": ["whatsapp", "email"],
                 "whatsapp_active": True,
                 "email_active": True}
}

ℹ️ Si el token es inválido o no existe, se lanza DojaAuthError. No se lanza DojaQuotaExceeded aunque la cuota esté agotada — la idea es justamente que puedas detectarlo leyendo q["can_send"] o q["messages"]["remaining"].


5. Módulo Asíncrono (AsyncIO) ⚡

Ideal para aplicaciones modernas como FastAPI o procesos que requieren alta concurrencia. El SDK asíncrono utiliza httpx bajo el capó para realizar operaciones no bloqueantes.

WhatsApp Asíncrono

import asyncio
from doja_sdk import AsyncDojaClient

async def main():
    wa = AsyncDojaClient(
        doja_token="TU-LICENCIA-DOJA",
        whatsapp_token="EAAB...",
        phone_id="123456789"
    )

    # Envío no bloqueante
    await wa.send_text("525512345678", "¡Hola desde Async SDK!")

asyncio.run(main())

Email Asíncrono

from doja_sdk import AsyncDojaEmailClient

async def send_welcome():
    email = AsyncDojaEmailClient(
        doja_token="TU-LICENCIA-DOJA",
        external_id="LLAVE_RESEND",
        from_address="hola@tuempresa.com"
    )

    await email.send(
        to="cliente@ejemplo.com",
        subject="Bienvenido",
        body="<h1>Registro Exitoso</h1>",
        html=True
    )

6. Manejo de Errores 🛡️

El SDK expone excepciones estandarizadas que te permiten saber exactamente qué falló (si fue un problema de saldo, de licencia o de la API destino). Las puedes importar todas desde doja_sdk:

from doja_sdk import (
    DojaClient,
    DojaAuthError,
    DojaQuotaExceeded,
    DojaAPIError
)

try:
    client = DojaClient(...)
    client.send_text(...)

except DojaAuthError as e:
    # 404 No encontrado, 422 Inválido, 409 Suspendido, o no tiene el canal (WhatsApp/Email) pagado
    print(f"Problema con la Licencia: {e}")

except DojaQuotaExceeded as e:
    # 409 La licencia es válida, pero el cliente se acabó su bolsa de mensajes
    print(f"Saldo Agotado: {e}")

except DojaAPIError as e:
    # Ocurre si Meta o Resend rechazan la petición (Ej: plantilla mal armada, mal teléfono, adjunto pesado)
    print(f"Problema con el Proveedor Destino: {e}")

7. Optimización y Desempeño 🚀

Esta versión del SDK incluye mejoras críticas:

  • Caché de Licencia: La validación de licencia se cachea localmente durante 5 minutos para evitar picos de latencia en cada mensaje enviado.
  • Logging: Hemos reemplazado los print por el módulo logging. Puedes configurar el nivel de detalle:
    import logging
    logging.getLogger("doja_sdk").setLevel(logging.DEBUG)
    
  • Consumo Robusto: Las llamadas de consumo de créditos se ejecutan con manejadores de errores internos para asegurar que un fallo en el servidor de licencias no interrumpa tu proceso principal.

¿Dudas adicionales o soporte técnico?
Comunícate directamente a: contact@dojaconsulting.cloud

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

doja_sdk-0.10.0.tar.gz (26.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

doja_sdk-0.10.0-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

Details for the file doja_sdk-0.10.0.tar.gz.

File metadata

  • Download URL: doja_sdk-0.10.0.tar.gz
  • Upload date:
  • Size: 26.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for doja_sdk-0.10.0.tar.gz
Algorithm Hash digest
SHA256 0bbbfaa26d0fe6e2e8794c0de23c18f48d2c54152c1c5daf369a9e3eb7816b14
MD5 50df96eec7854beb693aeccad7229acc
BLAKE2b-256 3697febe5c74d2cd27567d736587807e109d596a2e8be95ad8e0b727b2d93d65

See more details on using hashes here.

File details

Details for the file doja_sdk-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: doja_sdk-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 22.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for doja_sdk-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 93a79fceb24f63f57b72093ceab5345a1939d28a2b20940aebc2f20f8ad441df
MD5 ef5330fce774a266427cd590ddbd5586
BLAKE2b-256 e21b637ed6c1fb2650f383e2111dda708d3d4c0b5f255757349db9cfd64bcb84

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page