Si usas Instagram para vender, ya conoces el truco: “Comenta GUÍA y te lo mando por DM”. Funciona muy bien, pero responder a mano a cada comentario es imposible, y herramientas como ManyChat cobran más cuantos más contactos tienes.
OpenReply es una alternativa de código abierto (licencia MIT) que hace exactamente eso: cuando alguien comenta una palabra clave en tu publicación o reel, le envía un mensaje privado de forma automática, usando la API oficial de Instagram. Sin compartir tu contraseña y sin automatizar el navegador. La ventaja es que corre en tu propio servidor, así que no pagas por contacto.
En esta guía te cuento, paso a paso, cómo lo instalé en Coolify (el panel self-hosted con el que gestiono mis servidores), con capturas de la instalación real y los errores que conviene evitar.

Qué vas a montar
Todo vive dentro de un único servicio Docker Compose en Coolify, con cinco contenedores:
- web: el panel de OpenReply y el endpoint que recibe los webhooks de Meta. Es el único que se expone a internet.
- worker: el proceso que envía los DMs desde una cola.
- cron: renueva los tokens de Instagram y lanza las tareas periódicas.
- PostgreSQL 16: la base de datos de campañas, contactos y registros.
- Redis 7: la cola de envíos.
El flujo es sencillo: Meta avisa a OpenReply cada vez que alguien comenta. Si el comentario contiene la palabra clave de una campaña, la web pone el envío en la cola de Redis y el worker manda el DM por la API oficial. El cron se encarga de que los tokens no caduquen.
Lo que necesitas antes de empezar
- Un servidor (VPS) con Coolify instalado.
- Un dominio con acceso a su DNS para crear un subdominio, por ejemplo
reply.tudominio.com. - Una cuenta de Instagram profesional (Business o Creator). Las cuentas personales no se pueden conectar.
- Una cuenta en Meta for Developers con acceso al portafolio de Meta Business de la marca.
- Una cuenta de Resend con tu dominio verificado (o un servidor SMTP propio). OpenReply no usa contraseñas: entras con un enlace mágico que llega al correo, así que sin email nadie puede iniciar sesión.
Parte 1: instalación en Coolify
Paso 1. Apunta el subdominio al servidor
En el panel DNS de tu dominio crea un registro A con el nombre reply y, como valor, la IP pública del servidor donde corre Coolify. Espera a que propague (normalmente unos minutos): Coolify solo podrá sacar el certificado SSL cuando el dominio ya resuelva.
Paso 2. Genera los secretos
Necesitas cuatro valores aleatorios. Puedes generarlos en cualquier terminal con OpenSSL y guardarlos en tu gestor de contraseñas (nunca en un documento compartido):
openssl rand -base64 32 # NEXTAUTH_SECRET
openssl rand -base64 32 # CRON_SECRET
openssl rand -hex 32 # ENCRYPTION_KEY (64 caracteres hex)
openssl rand -hex 16 # WEBHOOK_VERIFY_TOKEN
Atención: ENCRYPTION_KEY cifra los tokens de Instagram. Si la cambias o la pierdes, tendrás que volver a conectar todas las cuentas. Además, tiene que ser la misma en la web y en el worker.
Paso 3. Crea el servicio Docker Compose
OpenReply no tiene una imagen publicada en Docker Hub. La forma más cómoda es que Coolify la construya directamente desde el repositorio de GitHub con build.context: así no tienes que entrar por SSH ni compilar nada a mano. La imagen resultante (openreply-app:latest) se reutiliza en la web, el worker y el cron.
En Coolify entra en tu proyecto, pulsa + New, elige Docker Compose Empty, ponle de nombre openreply y pega este compose:
services:
web:
build:
context: 'https://github.com/diwenne/openreply.git#main'
image: 'openreply-app:latest'
restart: unless-stopped
command: ['sh', '-c', 'npx prisma migrate deploy && npm run start']
environment:
- SERVICE_FQDN_WEB_3000
- 'NEXTAUTH_URL=https://reply.tudominio.com'
- 'NEXTAUTH_SECRET=${NEXTAUTH_SECRET}'
- 'CRON_SECRET=${CRON_SECRET}'
- 'ENCRYPTION_KEY=${ENCRYPTION_KEY}'
- 'DATABASE_URL=postgresql://openreply:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/openreply'
- 'REDIS_URL=redis://redis:6379'
- 'RESEND_API_KEY=${RESEND_API_KEY:-}'
- 'EMAIL_SERVER=${EMAIL_SERVER:-}'
- 'EMAIL_FROM=${EMAIL_FROM}'
- 'ALLOWED_EMAILS=${ALLOWED_EMAILS}'
- META_GRAPH_API_VERSION=v25.0
- 'INSTAGRAM_APP_ID=${INSTAGRAM_APP_ID:-}'
- 'INSTAGRAM_APP_SECRET=${INSTAGRAM_APP_SECRET:-}'
- 'FACEBOOK_APP_SECRET=${FACEBOOK_APP_SECRET:-}'
- 'WEBHOOK_VERIFY_TOKEN=${WEBHOOK_VERIFY_TOKEN}'
depends_on:
postgres: { condition: service_healthy }
redis: { condition: service_healthy }
healthcheck:
test: ['CMD-SHELL', 'wget -q -O /dev/null http://127.0.0.1:3000/api/auth/csrf || exit 1']
interval: 15s
timeout: 10s
retries: 10
start_period: 60s
worker:
image: 'openreply-app:latest'
restart: unless-stopped
command: ['npm', 'run', 'worker']
environment:
- 'DATABASE_URL=postgresql://openreply:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/openreply'
- 'REDIS_URL=redis://redis:6379'
- 'ENCRYPTION_KEY=${ENCRYPTION_KEY}'
- 'NEXTAUTH_SECRET=${NEXTAUTH_SECRET}'
- 'NEXTAUTH_URL=https://reply.tudominio.com'
- META_GRAPH_API_VERSION=v25.0
- 'INSTAGRAM_APP_ID=${INSTAGRAM_APP_ID:-}'
- 'INSTAGRAM_APP_SECRET=${INSTAGRAM_APP_SECRET:-}'
- 'FACEBOOK_APP_SECRET=${FACEBOOK_APP_SECRET:-}'
depends_on:
web: { condition: service_started }
cron:
image: 'openreply-app:latest'
restart: unless-stopped
command: ['sh', 'scripts/cron.sh']
environment:
- 'CRON_BASE_URL=http://web:3000'
- 'CRON_SECRET=${CRON_SECRET}'
depends_on:
web: { condition: service_healthy }
postgres:
image: 'postgres:16'
restart: unless-stopped
environment:
- POSTGRES_DB=openreply
- POSTGRES_USER=openreply
- 'POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}'
volumes:
- 'openreply-pgdata:/var/lib/postgresql/data'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U openreply -d openreply']
interval: 5s
timeout: 5s
retries: 10
redis:
image: 'redis:7-alpine'
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--maxmemory-policy', 'noeviction']
volumes:
- 'openreply-redisdata:/data'
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 5s
retries: 10

Algunos detalles de este compose que me ahorraron problemas:
- SERVICE_PASSWORD_POSTGRES y SERVICE_FQDN_WEB_3000 los rellena Coolify solo: la primera es una contraseña aleatoria para la base de datos y la segunda le dice que la web escucha en el puerto 3000.
- Las variables de Meta llevan
:-(valor vacío por defecto) para que puedas desplegar antes de tener la app de Instagram creada. - Redis va con
appendonlyynoeviction: si Redis se reinicia o se llena, no pierdes envíos que estaban en la cola. - El healthcheck de la web consulta
/api/auth/csrf, una ruta ligera que responde en cuanto la app arranca.
Al guardar, Coolify muestra los cinco recursos del servicio:

Paso 4. Carga las variables de entorno
En el servicio abre Environment Variables. Coolify ya habrá detectado todas las variables que usa el compose (aparecen con la etiqueta “Used by Compose”); solo tienes que rellenarlas. Marca como Literal las que generaste con OpenSSL para que Coolify no interprete sus símbolos.

| Variable | Para qué sirve | De dónde sale |
|---|---|---|
| NEXTAUTH_SECRET | Firma las sesiones | Paso 2 (OpenSSL) |
| CRON_SECRET | Protege las tareas programadas | Paso 2 (OpenSSL) |
| ENCRYPTION_KEY | Cifra los tokens de Instagram | Paso 2 (64 hex) |
| WEBHOOK_VERIFY_TOKEN | Verifica el webhook de Meta | Paso 2 |
| RESEND_API_KEY | Envía el enlace mágico de acceso | Resend → API Keys |
| EMAIL_FROM | Remitente del correo | Ej.: OpenReply <[email protected]> |
| EMAIL_SERVER | Alternativa SMTP a Resend (opcional) | Tu proveedor de correo |
| ALLOWED_EMAILS | Correos que pueden entrar | Tus correos, separados por comas |
| INSTAGRAM_APP_ID | ID de la app de Instagram | Parte 2 |
| INSTAGRAM_APP_SECRET | Secreto de la app de Instagram | Parte 2 |
| FACEBOOK_APP_SECRET | Valida la firma de los webhooks | Parte 2 |
No dejes vacía ALLOWED_EMAILS. Si lo haces, cualquiera que entre en tu dominio puede pedir un enlace y crearse un espacio de trabajo en tu servidor.
Paso 5. Asigna el dominio y despliega
En Domains, asigna al contenedor web el dominio https://reply.tudominio.com con el puerto interno 3000. Solo la web lleva dominio: el worker, el cron, Postgres y Redis no se exponen a internet. Activa la redirección de HTTP a HTTPS.

Pulsa Deploy. El primer despliegue tarda unos minutos porque Coolify clona el repositorio y construye la imagen. Al arrancar, la web aplica las migraciones de la base de datos (prisma migrate deploy). Cuando termine, Postgres, Redis y web deben aparecer como healthy. El worker y el cron salen como unknown porque no tienen healthcheck propio: es normal.
Paso 6. Comprueba que todo responde
Abre https://reply.tudominio.com/api/health en el navegador. Debe indicar que la base de datos, Redis, la cola y el worker están bien:

Si worker.healthy sale en false, los DMs no se enviarán: revisa los logs del contenedor worker en Coolify.
Paso 7. Primer acceso
Entra en https://reply.tudominio.com, pulsa Sign in y escribe un correo que esté en ALLOWED_EMAILS. Abre el enlace que te llega y ya estás dentro. Si el correo no llega, revisa que el dominio de EMAIL_FROM esté verificado en Resend.
Parte 2: la app de Instagram en Meta for Developers
Paso 1. Pasa Instagram a cuenta profesional
En la app de Instagram: Configuración → Tipo de cuenta y herramientas y cambia a cuenta Business o Creator.
Paso 2. Crea la app en Meta
- Entra en
developers.facebook.com/appsy pulsa Crear app. - Como caso de uso, filtra por Todos y elige Administrar mensajes y contenido en Instagram.
- Tipo de app: Negocio. Asóciala al portafolio de Meta Business de la marca y pon un correo de contacto que revises.
No elijas Marketing API ni Inicio de sesión con Facebook. OpenReply usa el inicio de sesión con Instagram; con la variante de Facebook te aparecerá después un error de cliente que no coincide.
Paso 3. Copia los identificadores a Coolify
- INSTAGRAM_APP_ID e INSTAGRAM_APP_SECRET: en el producto Instagram → Configuración de la API con inicio de sesión de Instagram (pulsa Mostrar). Ojo: no es el mismo número que el ID de la app de Facebook.
- FACEBOOK_APP_SECRET: Configuración de la app → Básica → Clave secreta de la app.
Pégalas en las variables del servicio y vuelve a desplegar.
Paso 4. Añade tu Instagram como evaluador
Mientras la app no pase la revisión de Meta, solo funciona con cuentas que tengan un rol en ella. Hay que completar las dos mitades:
- En la app de Meta: Roles de la app → Roles → Evaluadores de Instagram, añade el usuario exacto y envía la invitación.
- En el móvil, desde esa cuenta de Instagram: Perfil → menú → Configuración y actividad → Apps y sitios web → Invitaciones de evaluador y acepta.
Si falta una de las dos, al conectar verás el error «Insufficient Developer Role».
Paso 5. URL de redirección (OAuth)
En el producto Instagram → Configurar el inicio de sesión empresarial de Instagram, añade en URI de redireccionamiento de OAuth válidos exactamente esta dirección, sin barra final:
https://reply.tudominio.com/api/instagram/callback
Paso 6. Configura el webhook
- Producto Instagram → Configurar webhooks.
- URL de devolución de llamada:
https://reply.tudominio.com/api/webhook - Token de verificación: el mismo valor de WEBHOOK_VERIFY_TOKEN. Pulsa Verificar y guardar.
- Suscríbete a comments y messages. Con solo comments funciona el comentario → DM, pero no las respuestas a DMs ni a historias.
- Prueba con el botón Probar junto a comments → Enviar a mi servidor, y comprueba que llega en los logs del contenedor web.
Usa siempre el dominio exacto que pusiste en Coolify. Si el dominio redirige (por ejemplo, de www a sin www), Meta no sigue la redirección y los webhooks dejan de llegar sin avisar.
Paso 7. Páginas legales y publicar la app
En modo desarrollo, Meta solo envía los webhooks de prueba: los comentarios reales llegan cuando la app está en vivo. OpenReply ya trae las páginas legales que pide Meta, así que en Configuración de la app → Básica solo tienes que rellenar:
Política de privacidad: https://reply.tudominio.com/privacy
Condiciones del servicio: https://reply.tudominio.com/terms
Eliminación de datos: https://reply.tudominio.com/data-deletion
Después pulsa Publicar. La app usa los permisos instagram_business_basic, instagram_business_manage_comments e instagram_business_manage_messages. Para tus propias cuentas basta el acceso estándar; solo necesitas la revisión de Meta si vas a conectar cuentas de terceros.
Paso 8. Si venías de ManyChat, cédele el control a OpenReply
En Meta Business Suite → Configuración → Integraciones → Enrutamiento de conversaciones → Apps de socios, elimina ManyChat (o la herramienta anterior) y, en la app de OpenReply, activa Acceder a todas las conversaciones y Tomar el control de las conversaciones. Si no lo haces, los botones del DM fallan con el error «not the thread owner».
Parte 3: conecta Instagram y crea tu primera campaña
- En OpenReply ve a Settings → Connect Instagram y acepta los permisos.
- En Campaigns → New Campaign elige la publicación, la palabra clave (por ejemplo,
GUIA) y el mensaje del DM. Opcionalmente, una respuesta pública al comentario y pedir que te sigan antes de enviar el enlace. - Desde otra cuenta de Instagram comenta la palabra clave en esa publicación.
- Comprueba que llega el DM y que en DM Logs aparece como enviado.

El panel te muestra de un vistazo las campañas activas, los DMs enviados, los clics y las palabras clave que más se usan:

Y en Diagnostics tienes el estado del worker, la cola y cualquier fallo de webhooks o de renovación de tokens. Es la primera pantalla que miro cuando algo no funciona:

Mantenimiento
- Tokens de Instagram: el contenedor cron los renueva solo, una vez al día. Si el cron se detiene, las automatizaciones se paran sin dar error.
- Actualizar OpenReply: como la imagen se construye desde GitHub, basta con pulsar Redeploy en Coolify para traer la última versión de la rama main.
- Copias de seguridad: programa backups del volumen de Postgres desde Coolify.
Problemas frecuentes
| Síntoma | Causa y solución |
|---|---|
| No llega el correo de acceso | Dominio de EMAIL_FROM sin verificar en Resend, RESEND_API_KEY vacía o el correo no está en ALLOWED_EMAILS. |
| «Insufficient Developer Role» | La cuenta no aceptó la invitación de evaluador desde la app de Instagram. |
| Error de redirect_uri al conectar | La URL de OAuth en Meta no coincide exactamente con /api/instagram/callback. |
| Los comentarios reales no disparan nada | La app sigue en modo desarrollo, falta suscribir comments o el dominio redirige. |
| Los DMs no salen | Worker caído: revisa /api/health y los logs del worker. ENCRYPTION_KEY debe ser igual en web y worker. |
| «not the thread owner» | ManyChat sigue controlando las conversaciones. |
| Dejó de funcionar a las semanas | Token caducado porque el cron no corre: revisa el contenedor cron y CRON_SECRET. |
Preguntas frecuentes
¿OpenReply es gratis?
El software es libre y gratuito (licencia MIT). Lo que pagas es el servidor donde lo instalas y, si lo usas, el plan de Resend para los correos de acceso. Si ya tienes un VPS con Coolify, el coste extra es prácticamente nulo.
¿Es seguro para mi cuenta de Instagram?
Sí, porque usa la API oficial de Meta con los permisos que tú apruebas. No guarda tu contraseña ni simula clics en el navegador, que es lo que suele provocar bloqueos.
¿Necesito saber programar?
No hace falta programar, pero sí sentirte cómodo con Coolify, DNS y el panel de Meta for Developers. Si sigues los pasos en orden, la instalación se hace en una tarde.
¿Puedo conectar varias cuentas de Instagram?
Sí. Con cuentas propias que tengan rol en la app basta el acceso estándar. Para conectar cuentas de clientes u otras personas, Meta exige pasar la revisión de la app.
¿Quieres que lo monte por ti?
Si prefieres tener tus respuestas automáticas funcionando sin pelearte con Docker ni con Meta, puedo instalarlo y dejarlo configurado con tus campañas. Mira mi servicio de automatizaciones e IA o escríbeme y pídeme una auditoría gratuita.
