Correo (email)
Correo propio para cada negocio: conectás un dominio,
creás casillas (ej. ventas@tudominio.com) y alias, y
leés, borrás y enviás mensajes por API. Pensado para
que un agente de IA opere las casillas de forma autónoma. Sabado corre
el servidor de correo — vos nunca ves el token de administración ni las
contraseñas de las casillas.
Dos planos. La gestión (dominios, casillas, alias) es una API REST normal. Los mensajes (leer / enviar) corren sobre IMAP/SMTP por detrás, pero vos los usás con los mismos endpoints REST y la misma API key. No tenés que tocar IMAP ni SMTP directamente.
Flujo típico
- Conectás el dominio:
POST /v1/mail/domains→ te devolvemos los registros DNS a publicar. - El dueño del dominio carga esos registros (MX, SPF, DKIM, DMARC) en su proveedor de dominio. ¿El dominio está en Cloudflare? Desde el panel (sección Correo) puede conectar su cuenta de Cloudflare con un click y los registros se aplican solos, con su consentimiento y mostrando antes qué se va a crear o modificar (un SPF existente se combina, nunca se pisa; un DMARC existente se respeta).
- Verificás propagación:
POST /v1/mail/domains/{domain}/verifyhasta que dédns_verified: true. - Creás casillas:
POST /v1/mail/accounts. - Operás mensajes:
GET .../messages,POST .../send, etc.
Base de la API: https://waba.galgo.io —
todos los paths de esta página cuelgan de ahí (ej.
GET https://waba.galgo.io/v1/mail/domains).
Scopes: mail.read para listar/leer,
mail.write para crear/borrar/enviar. Si la plataforma
todavía no tiene el correo habilitado para tu cuenta, los endpoints
responden 503 not_configured.
Conectar un dominio
POST /v1/mail/domains
Da de alta el dominio, genera sus llaves DKIM y devuelve los registros DNS que hay que publicar.
Scope: mail.write.
POST /v1/mail/domains
{
"tenant_ref": "panaderia-lo-de-juan",
"domain": "panaderia.com"
}
Respuesta:
{
"tenant_ref": "panaderia-lo-de-juan",
"domain": "panaderia.com",
"dkim_generated": true,
"dns_verified": false,
"dns_verified_at": null,
"dns_records": {
"dns_mx": "panaderia.com. 600 IN MX 10 mail.sabado.cloud.",
"dns_spf": "panaderia.com. 600 IN TXT \"v=spf1 mx a:mail.sabado.cloud ~all\"",
"dns_dkim": "dkim._domainkey.panaderia.com. 600 IN TXT \"v=DKIM1; k=rsa; p=...\"",
"dns_dmarc": "_dmarc.panaderia.com. 600 IN TXT \"v=DMARC1; p=reject;\""
},
"created_at": "2026-06-01T12:00:00+00:00"
}
Mostrale dns_records al dueño del dominio para que los
cargue en su proveedor. El correo no funciona hasta que esos registros
propaguen.
GET /v1/mail/domains · GET /v1/mail/domains/{domain}
Lista los dominios del negocio, o trae uno con sus
dns_records y estado de verificación.
Scope: mail.read. Query:
tenant_ref (requerido).
GET /v1/mail/domains?tenant_ref=panaderia-lo-de-juan
Authorization: Bearer TU_API_KEY
DELETE /v1/mail/domains/{domain}
Borra el dominio (arrastra sus casillas y alias). Scope:
mail.write. Solo borra de nuestro lado si el servidor de
correo confirmó el borrado.
Errores:
404 tenant_not_found— eltenant_refno existe en tu cuenta.404 domain_not_found— el dominio no pertenece a ese negocio.409 conflict— el dominio ya existe.403 plan_not_included— el plan de tu cuenta no incluye correo. Pasate a un plan que lo incluya desde el panel.402 subscription_required— conectar un dominio requiere la suscripción activa de la cuenta (planes pagos).422 quota_exceeded— la cuenta ya usa todos los dominios de correo de su plan. El cupo es por cuenta, sumando todos tus negocios.503 not_configured— el correo no está habilitado en la plataforma.
Configurar el DNS
Los registros que devuelve POST /v1/mail/domains hay que
cargarlos en el proveedor del dominio. Son cuatro obligatorios:
| Tipo | Para qué |
|---|---|
| MX | Dónde se entrega el correo del dominio (apunta a nuestro servidor). |
| TXT (SPF) | Autoriza a nuestro servidor a enviar en nombre del dominio. |
| TXT (DKIM) | Firma criptográfica de los mensajes salientes. |
| TXT (DMARC) | Política de qué hacer con el correo que no pasa SPF/DKIM. |
POST /v1/mail/domains/{domain}/verify
Resuelve el DNS real y te dice qué registros ya propagaron. Marca el
dominio como verificado solo si MX + SPF + DKIM resuelven.
Scope: mail.write.
POST /v1/mail/domains/panaderia.com/verify
{ "tenant_ref": "panaderia-lo-de-juan" }
Respuesta:
{
"tenant_ref": "panaderia-lo-de-juan",
"domain": "panaderia.com",
"dns_checks": { "mx": true, "spf": true, "dkim": true, "all": true },
"dns_verified": true
}
El DNS puede tardar hasta 24-48hs en propagar.
Reintentá verify cada tanto; mientras
all sea false, todavía no está listo.
Crear casillas
POST /v1/mail/accounts
Crea una casilla en un dominio del negocio. La plataforma genera la contraseña y la usa internamente para enviar/recibir — no se devuelve nunca por la API.
Scope: mail.write.
POST /v1/mail/accounts
{
"tenant_ref": "panaderia-lo-de-juan",
"domain": "panaderia.com",
"local_part": "ventas",
"displayed_name": "Ventas Panadería",
"quota_bytes": 2147483648
}
local_part: lo que va antes del@(ej.ventas).displayed_name,quota_bytes: opcionales.
Respuesta:
{
"tenant_ref": "panaderia-lo-de-juan",
"email": "ventas@panaderia.com",
"displayed_name": "Ventas Panadería",
"quota_bytes": 2147483648,
"enabled": true,
"created_at": "2026-06-01T12:00:00+00:00"
}
GET /v1/mail/accounts · GET /v1/mail/accounts/{email}
Lista las casillas del negocio (filtrable con ?domain=) o
trae una. Scope: mail.read. Query:
tenant_ref (requerido).
PATCH /v1/mail/accounts/{email}
Actualiza quota_bytes, displayed_name,
enabled, o rota la contraseña con
"rotate_password": true. Scope:
mail.write.
DELETE /v1/mail/accounts/{email}
Borra la casilla. Scope: mail.write.
Errores:
404 domain_not_found— el dominio no pertenece al negocio.404 account_not_found— la casilla no pertenece al negocio.409 conflict— la casilla ya existe.402 subscription_required— la suscripción de la cuenta está suspendida (las casillas existentes siguen funcionando; solo se bloquea crear nuevas).422 quota_exceeded— se alcanzó el máximo de casillas del plan. El cupo es por cuenta, sumando las casillas de todos tus dominios y negocios.
Alias
Un alias reenvía una dirección a otra(s). Sirve para tener
info@, hola@, etc. cayendo en una casilla
real, sin crear buzones nuevos.
GET /v1/mail/aliases · POST /v1/mail/aliases · DELETE /v1/mail/aliases/{alias}
Scopes: mail.read (listar) /
mail.write (crear, borrar). El alias tiene que caer en un
dominio del negocio.
POST /v1/mail/aliases
{
"tenant_ref": "panaderia-lo-de-juan",
"alias": "info@panaderia.com",
"destination": "ventas@panaderia.com",
"wildcard": false
}
destination puede ser varias direcciones separadas por
coma. wildcard: true hace catch-all del dominio.
Leer y enviar mensajes
Estos endpoints operan el buzón de una casilla. Es lo que le da a un agente la capacidad de leer y responder correo de forma autónoma.
GET /v1/mail/accounts/{email}/messages
Lista mensajes (resúmenes, sin cuerpo). Scope:
mail.read. Query: tenant_ref (requerido),
folder (default INBOX), limit
(1–100, default 25). Listar no marca como leído.
GET /v1/mail/accounts/ventas@panaderia.com/messages?tenant_ref=panaderia-lo-de-juan
Authorization: Bearer TU_API_KEY
Respuesta:
{
"tenant_ref": "panaderia-lo-de-juan",
"email": "ventas@panaderia.com",
"folder": "INBOX",
"messages": [
{
"uid": 12,
"subject": "Pedido del sábado",
"from": ["Ana <ana@gmail.com>"],
"date": "2026-06-01T10:00:00+00:00",
"seen": false
}
]
}
GET /v1/mail/accounts/{email}/unseen
Resumen liviano de no leídos (sin cuerpos), pensado
para consultar el inbox seguido con costo mínimo — el caso típico es un
agente que revisa si hay algo nuevo cada un rato.
Scope: mail.read. Query:
tenant_ref (requerido), since_uid (opcional:
solo mensajes con UID mayor), folder (default
INBOX), limit (1–100, default 25).
GET /v1/mail/accounts/ventas@panaderia.com/unseen?tenant_ref=panaderia-lo-de-juan&since_uid=123
Authorization: Bearer TU_API_KEY
Respuesta:
{
"tenant_ref": "panaderia-lo-de-juan",
"email": "ventas@panaderia.com",
"folder": "INBOX",
"count": 2,
"latest_uid": 125,
"uidvalidity": 17,
"messages": [
{ "uid": 125, "subject": "Urgente", "from": ["Ana <ana@gmail.com>"], "date": "2026-07-31T10:00:00+00:00", "seen": false },
{ "uid": 124, "subject": "Pedido", "from": ["Leo <leo@gmail.com>"], "date": "2026-07-31T09:00:00+00:00", "seen": false }
]
}
count— total de no leídos que matchean (puede superar a los devueltos si excedelimit).latest_uid— último UID asignado en el buzón; guardalo como próximosince_uid(viene aunquecountsea 0).uidvalidity— si cambia entre consultas, el servidor reasignó los UIDs: descartá tusince_uidguardado y arrancá de nuevo.- Un mensaje marcado como leído (ver
PATCHabajo) deja de aparecer acá.
GET /v1/mail/accounts/{email}/messages/{uid}
Trae un mensaje completo (cuerpo en texto y HTML, destinatarios, si
tiene adjuntos). Scope: mail.read.
PATCH /v1/mail/accounts/{email}/messages/{uid}
Marca un mensaje como leído o no leído (es el flag \Seen
de IMAP). Leer por API no marca leído: este endpoint es el
único que toca el flag, así varios procesos pueden coordinar qué está
atendido sin llevar estado propio. Scope:
mail.write.
PATCH /v1/mail/accounts/ventas@panaderia.com/messages/124
{
"tenant_ref": "panaderia-lo-de-juan",
"seen": true
}
Respuesta: { "ok": true, "email": "ventas@panaderia.com", "uid": 124, "seen": true }
DELETE /v1/mail/accounts/{email}/messages/{uid}
Borra un mensaje del buzón. Scope: mail.write.
POST /v1/mail/accounts/{email}/send
Envía un correo desde la casilla. Requiere text o
html (o ambos). Scope:
mail.write.
POST /v1/mail/accounts/ventas@panaderia.com/send
{
"tenant_ref": "panaderia-lo-de-juan",
"to": ["cliente@gmail.com"],
"cc": [],
"reply_to": "ventas@panaderia.com",
"subject": "Tu pedido está listo",
"text": "Hola! Tu pedido te espera.",
"html": "Hola! Tu pedido te espera.
"
}
Respuesta:
{
"ok": true,
"tenant_ref": "panaderia-lo-de-juan",
"from": "ventas@panaderia.com",
"sent": true
}
Errores (mensajes):
404 account_not_found— la casilla no pertenece al negocio.404 folder_not_found— la carpeta no existe en el buzón.404 not_found— no existe un mensaje con ese UID en la carpeta.502 imap_error/502 smtp_error— falló la lectura o el envío (vermessage).
Privacidad. No guardamos ni mostramos el contenido de los correos en ningún panel nuestro — el cuerpo de los mensajes vive en el buzón, no en nuestra base. Las contraseñas de las casillas se guardan cifradas y nunca se devuelven por la API.