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
El SDK está organizado por canal. Cada canal es independiente: usa solo el que necesites.
- Configuración del cliente
- Envío de mensajes
- Envío de plantillas aprobadas
- Recepción de media entrante (webhooks)
- Administración de plantillas (CRUD)
- Crear plantillas con multimedia 🆕
- WhatsApp asíncrono
📧 Email 8. Configuración y envío 9. Email asíncrono
🧰 Común a ambos canales 10. Consulta de cuotas restantes 11. Manejo de errores 12. Optimización y desempeño
Todo lo relacionado con WhatsApp Cloud API vive aquí: enviar mensajes, plantillas, recibir media y administrar templates.
1.1 Configuración del cliente
Para interactuar con WhatsApp necesitas tu Token de Licencia DoJa, tu Token de Meta y tu Phone ID.
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}")
1.2 Envío de mensajes
(Nota: El destinatario debe incluir código de país sin el signo +. Ej: "525512345678")
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 = [
{"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
)
1.3 Envío de plantillas aprobadas
(Requerido para iniciar conversación fuera de la ventana de atención de 24 hrs. La plantilla debe estar APPROVED en Meta.)
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",
}
)
Plantilla con header de imagen (la imagen real al momento de enviar):
wa.send_template(
to="525512345678",
template_name="promo_junio",
language_code="es",
header_image_url="https://tu-cdn.com/promo.jpg",
body_variables=["Juan"]
)
[!NOTE]
body_variablesybody_variables_namedson mutuamente excluyentes. Una plantilla de WhatsApp usa o placeholders posicionales ({{1}}) o nombrados ({{nombre}}), nunca ambos en el mismo body. Si pasas los dos, el SDK lanzaDojaValidationError.
1.4 Recepción 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:
- Convierte el
media_iden la URL temporal que expone Meta (vida ~5 min, requiere bearer token). - Descarga el binario con el mismo token.
- 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):
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 que tu CRM muestre la imagen días o meses después, subemedia["content"]a tu propio almacenamiento (S3, R2, GCS) y guarda la URL pública resultante.
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_idvacío →DojaValidationError.media_idexpirado o inexistente →DojaAPIError(404).- Token de WhatsApp inválido →
DojaAPIError(401).
1.5 Administración de plantillas (CRUD)
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
PENDINGhasta que Meta las apruebe. Solo cuando esténAPPROVEDpodrás enviarlas conwa.send_template(...).
Configuración
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 (solo texto/botones)
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"}
📌 ¿Quieres una plantilla con header de imagen/video/documento? No basta con una URL: Meta exige un
header_handle. Mira la sección 1.6.
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")
1.6 Crear plantillas con multimedia 🆕
Disponible desde v0.10.0.
Para crear una plantilla con un header de imagen, video o documento, Meta no acepta una URL ni un media_id. Exige un header_handle: un identificador especial que se obtiene subiendo el archivo a la Resumable Upload API de Meta. Esto es lo mismo que hace la UI de Meta por detrás cuando arrastras una imagen al crear una plantilla.
El SDK lo resuelve con upload_media_handle, que hace la subida en dos pasos y te devuelve el handle listo para usar.
🔑 Requiere
app_id(el identificador público de tu App de Meta) en el constructor deDojaTemplateClient. No es un secreto: lo ves en App settings del dashboard de Meta for Developers.
from doja_sdk import DojaTemplateClient
tpl = DojaTemplateClient(
doja_token="TU-LICENCIA-DOJA",
whatsapp_token="EAAB12345...",
waba_id="123456789012345",
app_id="27063109049979489", # 🆕 requerido solo para subir media
)
# 1) Sube el archivo y obtén el header_handle
with open("promo.jpg", "rb") as f:
file_bytes = f.read()
handle = tpl.upload_media_handle(
file_bytes=file_bytes,
file_name="promo.jpg",
mime_type="image/jpeg",
)
# 2) Crea la plantilla usando el handle en example.header_handle
tpl.create_template(
name="promo_junio",
language="es_MX",
category="MARKETING",
components=[
{
"type": "HEADER",
"format": "IMAGE", # IMAGE | VIDEO | DOCUMENT
"example": {"header_handle": [handle]} # 👈 el handle del paso 1
},
{
"type": "BODY",
"text": "Hola {{1}}, tenemos una oferta para ti 🎉",
"example": {"body_text": [["Juan"]]}
}
]
)
# → {"id": "...", "status": "PENDING"}
Firma:
handle: str = tpl.upload_media_handle(file_bytes: bytes, file_name: str, mime_type: str)
Errores típicos:
- Cliente sin
app_id→DojaValidationError. file_bytesvacío →DojaValidationError.- Meta rechaza la subida (token sin permisos, tipo no soportado) →
DojaAPIError.
1.7 WhatsApp asíncrono
Ideal para apps modernas (FastAPI) o procesos con alta concurrencia. El SDK asíncrono usa httpx bajo el capó.
import asyncio
from doja_sdk import AsyncDojaClient
async def main():
wa = AsyncDojaClient(
doja_token="TU-LICENCIA-DOJA",
whatsapp_token="EAAB...",
phone_id="123456789"
)
await wa.send_text("525512345678", "¡Hola desde Async SDK!")
asyncio.run(main())
Las plantillas también tienen versión async — misma API, incluido upload_media_handle:
import asyncio
from doja_sdk import AsyncDojaTemplateClient
async def main():
tpl = AsyncDojaTemplateClient(
doja_token="TU-LICENCIA-DOJA",
whatsapp_token="EAAB...",
waba_id="123456789012345",
app_id="27063109049979489", # solo si vas a subir media
)
plantillas = await tpl.list_templates(limit=20)
print(plantillas)
asyncio.run(main())
El Módulo de Email está diseñado para ser extremadamente intuitivo. No requiere doble token: tu clave de licencia de DoJa funciona como la llave para despachar los correos mediante el proveedor subyacente (Resend).
2.1 Configuración y envío
from doja_sdk import DojaEmailClient, DojaAuthError
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}")
Texto plano:
email.send(
to="cliente@empresa.com",
subject="Confirmación de Registro",
body="Gracias por registrarte en nuestra plataforma."
)
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
)
Archivos adjuntos (rutas locales o URLs): (El SDK codifica archivos locales en Base64 o transfiere la URL 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
]
)
Múltiples destinatarios, CC, BCC y Reply-To:
email.send(
to="gerente@empresa.com, asistente@empresa.com", # separa 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"]
)
2.2 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
)
🧰 Común a ambos canales
3.1 Consulta de cuotas restantes
¿Quieres saber cuántos mensajes de WhatsApp y correos te quedan? Usa get_remaining_quotas. Solo necesitas tu doja_token — no requiere instanciar un cliente completo y no consume crédito.
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:
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")
Desde un cliente ya instanciado — cualquier cliente (DojaClient, DojaEmailClient, DojaTemplateClient...) expone los contadores como atributos:
wa = DojaClient(doja_token="...", whatsapp_token="...", phone_id="...")
print(wa.remaining_messages, wa.remaining_emails)
print(wa.used_messages, wa.used_emails)
print(wa.current_period_end)
quotas = wa.get_remaining_quotas() # refresca (golpea el core)
quotas = wa.get_remaining_quotas(use_cache=True) # caché de 5 min
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 lanzaDojaQuotaExceededaunque la cuota esté agotada — la idea es que lo detectes leyendoq["can_send"]oq["messages"]["remaining"].
3.2 Manejo de errores
El SDK expone excepciones estandarizadas que te permiten saber exactamente qué falló. Las importas todas desde doja_sdk:
from doja_sdk import (
DojaClient,
DojaAuthError,
DojaQuotaExceeded,
DojaAPIError,
DojaValidationError,
)
try:
client = DojaClient(...)
client.send_text(...)
except DojaAuthError as e:
# 404 No encontrado, 422 Inválido, 409 Suspendido, o canal no contratado
print(f"Problema con la Licencia: {e}")
except DojaQuotaExceeded as e:
# 409 La licencia es válida, pero se acabó la bolsa de mensajes
print(f"Saldo Agotado: {e}")
except DojaValidationError as e:
# Parámetros inválidos antes de salir a la red (ej. body_variables + named)
print(f"Datos inválidos: {e}")
except DojaAPIError as e:
# Meta o Resend rechazan la petición (plantilla mal armada, teléfono inválido, adjunto pesado)
print(f"Problema con el Proveedor Destino: {e}")
3.3 Optimización y desempeño
- Caché de licencia: la validación se cachea localmente 5 minutos para evitar picos de latencia en cada envío.
- Logging: el SDK usa el módulo
logging(noprint). Ajusta el nivel:import logging logging.getLogger("doja_sdk").setLevel(logging.DEBUG)
- Consumo robusto: las llamadas de consumo de créditos tienen manejadores de error internos para que un fallo del servidor de licencias no interrumpa tu proceso principal.
¿Dudas adicionales o soporte técnico? Comunícate directamente a: contact@dojaconsulting.cloud
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file doja_sdk-0.11.0.tar.gz.
File metadata
- Download URL: doja_sdk-0.11.0.tar.gz
- Upload date:
- Size: 28.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e87b5c099d2017af755af3ee15a1eb7792acd74eee6004f85d482d87f5795f64
|
|
| MD5 |
ae929f12adddf7cefe6e554fbb44c93c
|
|
| BLAKE2b-256 |
ce1377a45ca1bc61858409dda5d8e68294b03fea400eb292d9b26d8bf5be5f4b
|
File details
Details for the file doja_sdk-0.11.0-py3-none-any.whl.
File metadata
- Download URL: doja_sdk-0.11.0-py3-none-any.whl
- Upload date:
- Size: 23.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ed4a190970348876686ae3e27cd828f46338581b9944c1fd9904f9aa03e2732
|
|
| MD5 |
92e39e77d9cc78688cc7f738a8539dad
|
|
| BLAKE2b-256 |
995271f102e9749f4665b6949087b43bfaf6687f11085adac69d300fc5ca43b6
|