Documentación de MailMask
Actualizado:
Skills para tu agente, cliente oficial TypeScript/JavaScript, API REST, SMTP y servidor MCP. Crea direcciones, gestiona dominios, envía emails y más — desde tu código o pidiéndoselo a tu agente.
Recomendado
Usa MailMask con tu agente
Instala las skills en Claude Code, Cursor, Codex o Copilot y pídele lo que necesitas en español: “crea hola@ que reenvíe a mi Gmail”.
-
1. Instala las skills
npx skills add https://www.mailmask.studio -
2. Dale una API key
Inicia sesión y genérala aquí, o en Dashboard → API Keys. Es gratis.Sesión de .
-
3. Conecta el MCP
Con esa llave, o sin llave por OAuth (sólo la URL), el servidor MCP deja que el agente opere la cuenta por ti.
Qué trae cada una de las 5 skills
mailmask-account: opera la cuenta entera (dominios, máscaras, buzones, reglas, webhooks, envío).mailmask-mcp: conecta el cliente MCP y sabe leer sus respuestas.mailmask-sdk: usa@easybits.cloud/mailmaskdesde código.mailmask-dns: apunta el dominio a Vercel, Netlify o GitHub Pages sin romper el correo.mailmask-docs: lee esta documentación,llms.txty el OpenAPI.
Formato Agent Skills. Espejo: npx skills add blissito/mailmask-skills.
Instalación
npm install @easybits.cloud/mailmask
Autenticación
Para usar el SDK necesitas una API Key. Puedes crear una desde el Dashboard en la sección de API Keys. Disponible en todas las cuentas, gratis incluidas.
Las API keys tienen el prefijo mk_ y solo se muestran una vez al crearlas. Guárdala en un lugar seguro.
import { MailMask } from "@easybits.cloud/mailmask";
const mm = new MailMask({ apiKey: "mk_..." });
Dominios
Gestiona los dominios conectados a tu cuenta. Puedes listar, agregar, verificar y consultar el estado de salud DNS.
// Listar dominios
const domains = await mm.domains.list();
// Obtener un dominio
const domain = await mm.domains.get("domain-id");
// Agregar dominio: devuelve los registros DNS que hay que publicar
const { domain, dnsRecords, requiereActivacion } = await mm.domains.create("example.com");
dnsRecords.mx; // { type: "MX", name, value: "10 inbound-smtp.us-east-1.amazonaws.com", priority: 10 }
dnsRecords.verification; // { type: "TXT", name: "_amazonses.example.com", value }
dnsRecords.dkim; // 3 CNAME
dnsRecords.spf; // { type: "TXT", name, value: "v=spf1 include:amazonses.com ~all" }
// requiereActivacion: true si es tu 2.º dominio: se guarda pero no reenvía hasta activarlo ($99/mes)
// Eliminar dominio
await mm.domains.delete("domain-id");
// Verificar DNS
await mm.domains.verify("domain-id");
// Estado de salud
const health = await mm.domains.health("domain-id");
// Registros listos para pegar en tu registrador; con live los compara con el DNS público
const setup = await mm.domains.dnsSetup("domain-id", { live: true });
setup.records; // [{ type, name: "@", fqdn, value, level: "requerido" | "recomendado" | "opcional" }]
setup.registrarHint; // { provider, label, note } — en qué panel se pegan (Hostinger, GoDaddy, Cloudflare…)
Direcciones
Crea y administra las direcciones de tu dominio (antes las llamábamos alias; también son tus máscaras). Cada dirección reenvía correo a uno o múltiples destinos, guarda el correo en un buzón IMAP propio, o las dos cosas. El dominio gratis permite 5 direcciones; el activado, ilimitadas. * como dirección es el catch-all.
En el SDK el recurso es mm.addresses (mm.aliases sigue funcionando, es el mismo). En la API REST la ruta es /api/domains/:id/addresses, y /api/domains/:id/alias sigue respondiendo igual. El campo del cuerpo sigue llamándose alias (lo que va antes de la @).
// Listar direcciones
const direcciones = await mm.addresses.list("domain-id");
// Crear una dirección
const hello = await mm.addresses.create("domain-id", {
alias: "hello",
destinations: ["me@gmail.com"]
});
// Dirección con múltiples destinos
const team = await mm.addresses.create("domain-id", {
alias: "team",
destinations: ["alice@company.com", "bob@company.com"]
});
// Actualizar una dirección
await mm.addresses.update("domain-id", "hello", { enabled: false });
// Eliminar una dirección
await mm.addresses.delete("domain-id", "hello");
Buzones IMAP
En un dominio activado, una dirección puede tener un buzón real para leer y enviar desde Apple Mail, Outlook o Thunderbird (IMAP 993 y SMTP 465 con TLS). Buzones ilimitados con 10 GB compartidos por dominio, ampliables en bloques de +50 GB. La contraseña se devuelve una sola vez y MailMask no la guarda; si se pierde, se regenera desde el panel. Una dirección con buzón puede ir sin destinos de reenvío.
// Dirección con buzón (y sin reenvío)
const ventas = await mm.addresses.create("domain-id", { alias: "ventas", mailbox: true });
ventas.buzon; // { email, password, quotaBytes, imap: { host, port: 993 }, smtp: { host, port: 465 } }
ventas.errorBuzon; // texto si la dirección se creó pero el buzón no
// Buzón para una dirección que ya existe
const buzon = await mm.addresses.createMailbox("domain-id", "hello");
// Borrar el buzón Y SU CORREO (la dirección conserva sus destinos)
// Contraseña nueva (se devuelve una sola vez)
const { password } = await mm.addresses.resetMailboxPassword("domain-id", "hello");
await mm.addresses.deleteMailbox("domain-id", "hello");
// Perfil de Apple Mail (plist, texto) para iPhone, iPad y Mac
const plist = await mm.addresses.appleProfile("domain-id", "hello");
// Exportar todo el buzón en .mbox: devuelve el Response en streaming (puede pesar GB)
const res = await mm.addresses.exportMbox("domain-id", "hello");
DNS
El editor de DNS viene con el dominio activado, y crear o editar registros es cosa del dueño o un administrador: un agente de la Bandeja puede verlos, no cambiarlos. Si MailMask lleva el DNS de tu dominio, puedes editar sus registros desde el SDK. Para un dominio que registraste fuera, primero se crea la zona: copiamos lo que encontremos de tu proveedor actual y te damos los nameservers que hay que cambiar en tu registrador. Hasta que los cambies, nada de lo que edites tiene efecto.
// Estado de la zona y registros actuales
const { zone, records } = await mm.dns.list("domain-id");
console.log(zone.status); // "none" | "pending_delegation" | "active"
// Crear la zona (importa tu DNS actual y devuelve los nameservers)
const z = await mm.dns.createZone("domain-id");
console.log(z.nameservers, z.imported);
// ¿Ya apunta a nosotros?
const d = await mm.dns.delegation("domain-id");
console.log(d.delegated, d.observed);
upsert reemplaza el conjunto de valores de ese nombre y tipo. Para añadir un valor, lee primero con list e incluye también los que ya estaban.
// Crear o reemplazar un registro
await mm.dns.upsert("domain-id", {
name: "www", // "@" para la raíz
type: "CNAME",
values: ["cname.vercel-dns.com"],
ttl: 300,
});
// En MX la prioridad va dentro del valor
await mm.dns.upsert("domain-id", { name: "sub", type: "MX", values: ["10 mail.ejemplo.com"] });
// Borrar un registro completo
await mm.dns.delete("domain-id", "www", "CNAME");
// Apuntar el dominio a un hosting sin saber qué registros hacen falta
await mm.dns.preset("domain-id", "vercel", "mi-proyecto.vercel.app");
// vercel | netlify | github-pages | cloudflare-pages | render | fly | redirect-a-www | dmarc
Los registros de tu correo están protegidos. El MX, el TXT de verificación y los CNAME de DKIM vienen marcados con managed: true y no se pueden borrar: si los quitas dejas de recibir correo. El TXT de la raíz sí se puede editar mientras conserve el SPF de MailMask; si lo omites, la respuesta trae suggestedValues con los valores ya fusionados.
Reglas
Las reglas permiten enrutar o filtrar emails automáticamente según el destinatario, el remitente o el asunto. Vienen con el dominio activado (el dominio gratis no incluye reglas).
field:to,fromosubject.match:contains,equalsoregex. Un patrón regex peligroso se rechaza con 400.action:forward(a un correo),webhook(a una URL pública) odiscard.targetsólo aplica a las dos primeras.
// Listar reglas
const rules = await mm.rules.list("domain-id");
// Crear regla
const rule = await mm.rules.create("domain-id", {
field: "from",
match: "contains",
value: "@newsletter.com",
action: "forward",
target: "newsletters@gmail.com"
});
// Actualizar regla
await mm.rules.update("domain-id", "rule-id", { enabled: false });
// Eliminar regla
await mm.rules.delete("domain-id", "rule-id");
Envío de emails
Envía emails individuales o masivos desde cualquier dirección activa de tu dominio. El cuerpo puede ir como html, body (texto plano) o markdown; replyTo es opcional.
La firma del dominio —y su logo— sólo se añade cuando mandas markdown. Con html o body el correo sale tal cual lo escribiste, sin firma: así un correo transaccional que ya trae su propio pie no termina con dos firmas.
- El dominio gratis no inicia correo nuevo: responde
403hasta que se activa ($99/mes, 50 envíos al día y bloques de +100). - El tope diario se cuenta por dominio; al alcanzarlo responde
429. - El HTML se limita a 100 KB (
413): Gmail recorta mensajes más grandes.
// Enviar email
await mm.send.send("domain-id", {
from: "hello",
fromName: "Mi Tienda",
to: "client@example.com",
subject: "Hola!",
html: "<p>Contenido del email</p>"
});
// Envío masivo
const job = await mm.send.bulkSend("domain-id", {
from: "hello",
recipients: ["a@test.com", "b@test.com"],
subject: "Newsletter",
html: "<p>Contenido</p>"
});
// Consultar estado del envío masivo
const status = await mm.send.bulkStatus("domain-id", job.jobId);
console.log(status.sent, status.failed, status.skippedSuppressed); // skipped: en lista de supresión
Copias, hilo y adjuntos
cc y bcc aceptan hasta 20 direcciones y pasan por la lista de supresión. Para que la respuesta caiga en el hilo del cliente, manda inReplyTo y references con el Message-ID original. Los adjuntos se suben primero (máx. 5 MB, sin ejecutables) y la llave se pasa al enviar; el archivo se borra de S3 en cuanto sale el correo.
// 1. Subir el archivo
const pdf = await mm.attachments.upload("domain-id", {
filename: "factura-0042.pdf",
contentType: "application/pdf",
data: await fs.readFile("./factura.pdf"), // Uint8Array o Blob
});
// 2. Enviar con copias, hilo y adjunto
await mm.send.send("domain-id", {
from: "facturacion",
to: "cliente@example.com",
cc: ["contador@example.com"],
bcc: ["archivo@midominio.com"],
subject: "Re: Factura 0042",
markdown: "Adjunto la **factura 0042**.",
inReplyTo: "<abc123@mail.example.com>",
references: "<abc123@mail.example.com>",
attachments: [pdf],
});
Imágenes dentro del correo
Para incrustar una imagen en el HTML de un correo, súbela al dominio (PNG, JPG, GIF o WebP; máx. 2 MB) y usa la URL pública que regresa. Es efímera: se borra tras enviarse el correo, o en el barrido diario si nunca se usó. Desde la 0.4.7.
const { url } = await mm.domains.uploadImage("domain-id", imagen); // Blob
await mm.send.send("domain-id", {
from: "hola", to: "cliente@example.com", subject: "Nuestro catálogo",
html: `<img src="${url}" alt="Catálogo">`,
});
Idempotencia
Si tu proceso reintenta, pasa una clave de idempotencia (máx. 128 caracteres). Durante 24 horas, la misma clave devuelve la respuesta original sin volver a enviar ni consumir cuota. La respuesta repetida lleva el header Idempotent-Replayed: true.
await mm.send.send("domain-id", { to, subject, html }, {
idempotencyKey: `pedido-${pedido.id}`,
});
Logs
Consulta el historial de emails procesados por un dominio. Los reenvíos entrantes llevan forwarded, discarded, failed o rule_matched. Los envíos que haces por API nacen en sent y, cuando SES avisa, pasan a delivered, bounced o complained; el motivo del rebote va en error.
// Últimos 50 por omisión
const logs = await mm.logs.list("domain-id");
// Hasta 100 (máximo)
const more = await mm.logs.list("domain-id", { limit: 100 });
Lista de supresión
Cuando un destinatario rebota de forma permanente o marca tu correo como spam, SES lo avisa y MailMask lo agrega a la lista de supresión del dominio. Desde entonces send, bulkSend y el reenvío lo saltan: insistirle daña la reputación de tu dominio. Un envío a una dirección suprimida responde 422.
// Ver quién está bloqueado y por qué
const lista = await mm.suppressions.list("domain-id");
// [{ email, reason: "bounce:Permanent" | "complaint" | "manual", createdAt }]
// Bloquear a mano (p. ej. alguien que pidió no recibir más)
await mm.suppressions.add("domain-id", "persona@example.com");
// Liberar tras corregir la dirección
await mm.suppressions.remove("domain-id", "persona@example.com");
Webhooks
Recibe un POST en tu servidor cada vez que pasa algo en el dominio. Disponible en dominio activado. La URL debe ser https y pública; máximo 10 webhooks por dominio.
email.received: llegó correo a una dirección.data: { from, to, subject, alias, forwardedTo[], messageId }email.sent: saliste porsendobulkSend.data: { to, cc?, subject, from, messageId, bulkJobId? }email.delivered: el servidor del destinatario aceptó el correo.data: { recipient, messageId, subject, smtpResponse, processingTimeMillis }email.bounced: rebote permanente; el destinatario entró a la lista de supresión.data: { recipient, reason, messageId, subject }email.complained: el destinatario marcó spam. Mismodataque el rebote.
// Crear. El secret se muestra una sola vez: guárdalo.
const wh = await mm.webhooks.create("domain-id", {
url: "https://miapp.com/webhooks/mailmask",
events: ["email.received", "email.bounced"],
});
console.log(wh.secret); // whsec_...
// Probar: encola un evento `ping` que llega en el siguiente minuto
await mm.webhooks.test("domain-id", wh.id);
// Ver las últimas 50 entregas, con intentos y código HTTP
const entregas = await mm.webhooks.deliveries("domain-id", wh.id);
// Pausar, editar, borrar
await mm.webhooks.update("domain-id", wh.id, { enabled: false });
await mm.webhooks.delete("domain-id", wh.id);
Verificar la firma
Cada entrega lleva X-MailMask-Event, X-MailMask-Delivery, X-MailMask-Timestamp (ms Unix) y X-MailMask-Signature = sha256=HMAC(secret, timestamp + "." + cuerpo). Verifica con el cuerpo crudo, antes de parsear el JSON. El SDK trae la función; corre en Node, Deno, Bun y Workers.
import { verifyWebhookSignature } from "@easybits.cloud/mailmask";
app.post("/webhooks/mailmask", async (req, res) => {
const raw = req.body.toString(); // cuerpo sin parsear
const ok = await verifyWebhookSignature(process.env.MAILMASK_WEBHOOK_SECRET, {
signature: req.headers["x-mailmask-signature"],
timestamp: req.headers["x-mailmask-timestamp"],
}, raw);
if (!ok) return res.status(401).end();
const { event, data } = JSON.parse(raw);
if (event === "email.bounced") await marcarCorreoInvalido(data.recipient);
res.status(200).end(); // responde 2xx rápido; procesa después
});
Reintentos: si tu servidor no responde 2xx en 10 segundos, MailMask reintenta a 1 min, 5 min, 30 min, 2 h y 12 h; después marca la entrega como failed. Las entregas se conservan 7 días.
Credenciales SMTP
Crea credenciales SMTP para enviar emails desde tu código o aplicaciones SaaS. Disponible en dominio activado.
// Listar credenciales
const creds = await mm.smtp.list("domain-id");
// Crear credencial
const cred = await mm.smtp.create("domain-id", "Mi app");
console.log(cred.server, cred.port, cred.username); // email-smtp.<region>.amazonaws.com, 587, STARTTLS
console.log(cred.password); // Solo se muestra una vez
// Revocar credencial
await mm.smtp.revoke("domain-id", "cred-id");
API Keys
Administra tus API keys programáticamente. Útil para rotación automática de credenciales.
// Listar keys
const keys = await mm.apiKeys.list();
// Crear key
const newKey = await mm.apiKeys.create("Mi nueva key");
console.log(newKey.key); // mk_... — solo se muestra una vez
// Revocar key
await mm.apiKeys.revoke("key-id");
Cobro, compra de dominios y equipo
Todo cobro es una liga de MercadoPago que abre y paga una persona: el SDK nunca cobra por sí solo, y nada queda activado hasta que MercadoPago confirma el pago. Compruébalo después con billing.addons() o registrations.list().
// Activación y add-ons (por dominio)
const { catalog, mine } = await mm.billing.addons();
const { init_point } = await mm.billing.checkout("domain-id", "domain"); // o "storage50", "sends100"
await mm.billing.status(); // suscripción legado, si existe
// Comprar un dominio (un año; se registra y configura solo al pagar)
const r = await mm.registrations.search("miempresa.com"); // { available, price (centavos MXN) }
const tlds = await mm.registrations.tlds();
const { initPoint, registrationId } = await mm.registrations.register("miempresa.com");
const regs = await mm.registrations.list();
await mm.registrations.renewal(registrationId); // liga de la renovación anual
await mm.registrations.transferOut(registrationId); // el código EPP llega por correo al dueño
// Transferir un dominio a MailMask
const check = await mm.transfers.check("miempresa.com"); // requisitos, precio y DNS actual; no cobra
const inv = await mm.transfers.dns(registrationId);
await mm.transfers.setDns(registrationId, records); // reemplaza el inventario completo
await mm.transfers.approveDns(registrationId);
await mm.transfers.resendEmail(registrationId);
// Equipo de la Bandeja
const { members, invites } = await mm.members.list("domain-id");
await mm.members.invite("domain-id", { email: "ana@empresa.com", name: "Ana", role: "agent" });
await mm.members.remove("domain-id", "member-id");
await mm.members.cancelInvite("domain-id", "token");
// Firma (markdown, máx. 2000; vacía la borra) y respuestas guardadas
await mm.signature.set("domain-id", "**Ana** · Ventas");
const { signature } = await mm.signature.get("domain-id");
const canned = await mm.canned.list("domain-id");
await mm.canned.create("domain-id", { title: "Gracias", body: "¡Gracias por escribir!" });
await mm.canned.delete("domain-id", "canned-id");
// Perfil de la cuenta: nombre y foto con los que te ve tu equipo en la Bandeja
const perfil = await mm.account.getProfile(); // { email, displayName, avatarUrl }
await mm.account.updateProfile({ displayName: "Ana Ruiz" });
await mm.account.setAvatar(archivo); // PNG, JPG o WebP, máx. 2 MB
await mm.account.removeAvatar();
// Identidad, plan y uso (por dominio y total) — desde la 0.4.7
const yo = await mm.account.me(); // { email, subscription, porDominio, usage, ... }
// Todo tu dato como JSON: direcciones, reglas y logs de cada dominio
const respaldo = await mm.account.export(); // { email, exportedAt, domains: [...] }
Activación anual
La activación del dominio también se paga por año: $999 MXN cada 12 meses en vez de $99 al mes. Por HTTP se pide con period: "annual" en POST /api/addons/checkout (por omisión "monthly"). Sólo aplica a kind: "domain": los bloques de espacio y envíos son mensuales y responden 400 si se piden anuales. El método billing.checkout() del SDK todavía no expone period.
POST /api/addons/checkout
{ "kind": "domain", "domainId": "domain-id", "period": "annual" }
// → { "init_point": "https://www.mercadopago.com.mx/...", "addonId": "..." }
Manejo de errores
El SDK lanza MailMaskError para respuestas HTTP no exitosas. Incluye el código de estado y el mensaje del servidor.
import { MailMask, MailMaskError } from "@easybits.cloud/mailmask";
try {
await mm.domains.create("example.com");
} catch (err) {
if (err instanceof MailMaskError) {
console.error(err.status, err.message);
}
}
CLI
CLI de terminal sobre este mismo SDK: un subcomando por recurso, sin modelo declarativo encima. Cubre la sesión (login, logout, whoami), domains, dns, aliases, webhooks, smtp, api-keys, rules, suppressions, inbox (la Bandeja), canned y signature, account, members, billing, registrations, transfers y referrals, el envío con send (email, bulk y status) y logs, con los buzones IMAP, el perfil de Apple Mail y la exportación en mbox.
Nombre del paquete y del binario (hoy mailmask-cli / mailmask) son provisionales, pendientes de confirmación antes de publicarse a npm.
¿Quien va a operar tu cuenta es un agente de código? Probablemente le convenga más instalarle las skills oficiales (npx skills add https://www.mailmask.studio) que esta CLI: ya saben qué puede hacer cada plan y qué secretos salen una sola vez. Esta CLI es para cuando la persona teclea los comandos ella misma.
Instalación
Todavía no está publicada en npm. Mientras tanto, se corre desde el repo:
# dentro de cli/
npm install
npm run build
node dist/index.js --help
Sesión
mailmask login [--api-key mk_...] [--base-url https://...]
mailmask whoami [--json]
mailmask logout
Sin --api-key, login abre el navegador para autorizar el dispositivo (device-code, como gh auth login) — no hace falta copiar y pegar nada. Con --api-key se salta ese paso y guarda la llave directo.
Dominios
mailmask domains list [--json]
mailmask domains get <dominio> [--json]
mailmask domains create <dominio> [--preset vercel|netlify|github-pages|cloudflare-pages|render|fly|redirect-a-www|dmarc] [--target ...] [--subdomain ...]
mailmask domains verify <dominio>
mailmask domains health <dominio>
mailmask domains delete <dominio> [--yes] [--json]
mailmask domains dns-setup <dominio> [--live] [--json]
mailmask domains logo set <dominio> <archivo> [--json]
mailmask domains logo remove <dominio> [--yes] [--json]
En todos los comandos con dominio, <dominio> acepta el nombre (acme.com) o el id — se resuelve contra domains list. create --preset registra el dominio y de una vez le aplica un preset de DNS. dns-setup muestra los registros a pegar en el registrador (--live los compara con el DNS público). logo set toma un archivo local (.png, .jpg, .jpeg o .webp, máx. 500 KB) y no acepta URL: el servidor sólo admite adjuntos firmados del chat del asistente.
DNS
mailmask dns list <dominio> [--json]
mailmask dns upsert <dominio> <nombre> <tipo> <valor...> [--ttl 300]
mailmask dns delete <dominio> <nombre> <tipo> [--yes] [--json]
mailmask dns preset <dominio> <preset> [--target ...] [--subdomain ...]
mailmask dns create-zone <dominio>
mailmask dns delegation <dominio>
mailmask dns import <dominio> [--json]
dns preset es la misma operación que domains create --preset, para cuando el dominio ya existe: apunta la raíz (y www, salvo que fijes --subdomain) a Vercel, Netlify, GitHub Pages, Cloudflare Pages, Render, Fly, un redirect a www o DMARC — sin escribir el registro a mano. dns upsert reemplaza el conjunto completo de valores de ese (nombre, tipo); los registros con managed: true (MX, verificación, DKIM, SPF) los protege el servidor y no se pueden tocar desde aquí. dns import es de sólo lectura: muestra los registros encontrados, los nameservers y el aviso, sin cambiar nada (2 cada 5 min; el límite sale con 5).
Reglas y supresiones
mailmask rules list <dominio> [--json]
mailmask rules create <dominio> --field to|from|subject --match contains|equals|regex --value <texto> --action forward|webhook|discard [--target <destino>] [--priority N] [--disabled] [--json]
mailmask rules update <dominio> <reglaId> [--field ...] [--match ...] [--value ...] [--action ...] [--target ...] [--priority N] [--enable | --disable] [--json]
mailmask rules delete <dominio> <reglaId> [--yes] [--json]
mailmask suppressions list <dominio> [--json]
mailmask suppressions add <dominio> <correo> [--json]
mailmask suppressions remove <dominio> <correo> [--yes] [--json]
forward y webhook exigen --target. Los flags se validan antes de tocar la red; la regex la valida el servidor, y una peligrosa sale con 1 y el mensaje del servidor.
Envío y logs
mailmask send email <dominio> --to <correo> --subject <asunto> [--from <alias>] [--from-name <nombre>] [--reply-to <correo>] [--cc <correo>]... [--bcc <correo>]... [--text <texto|@archivo> | --html <html|@archivo> | --markdown <md|@archivo>] [--attach <archivo>]... [--idempotency-key <clave>] [--yes] [--json]
mailmask send bulk <dominio> <archivo.json> [--yes] [--json]
mailmask send status <dominio> <jobId> [--json]
mailmask logs <dominio> [--limit 1-100] [--json]
--from es la parte local del alias (hola) o la dirección completa del mismo dominio (hola@acme.com); una de otro dominio falla sin tocar la red, y sin --from el correo sale desde noreply@ y el CLI lo avisa por stderr. --text, --html y --markdown aceptan el contenido o @archivo; --cc, --bcc y --attach se repiten (adjuntos de hasta 5 MB). send bulk lee un JSON con exactamente recipients, subject, html y, opcional, from; cualquier otra llave es error. Como salen a terceros, send email y send bulk piden confirmación en una terminal y exigen --yes fuera de ella; sin él salen con 1 sin llamar al SDK. Enviar pide el dominio activado.
Bandeja, respuestas guardadas y firma
mailmask inbox list <dominio> [--status open|snoozed|closed|unread|deleted] [--alias <máscara>] [--assigned <usuario>] [--q <texto>] [--limit N] [--cursor <c>] [--json]
mailmask inbox get <dominio> <conversación> [--before <mensaje>] [--json]
mailmask inbox reply <dominio> <conversación> --markdown <texto> [--cc <a,b>] [--bcc <a,b>] [--no-quote] [--yes] [--json]
mailmask inbox compose <dominio> --from <máscara> --to <correo> --subject <asunto> --markdown <texto> [--cc <a,b>] [--bcc <a,b>] [--yes] [--json]
mailmask inbox update <dominio> <conversación> [--status open|snoozed|closed] [--snooze-until <ISO>] [--tags <a,b>] [--priority normal|urgent] [--json]
mailmask inbox read <dominio> <conversación> [--json]
mailmask inbox assign <dominio> <conversación> [usuario] [--json]
mailmask inbox note <dominio> <conversación> <texto> [--json]
mailmask inbox delete <dominio> <conversación...> [--yes] [--json]
mailmask inbox restore <dominio> <conversación> [--json]
mailmask inbox metrics <dominio> [--days N] [--json]
mailmask inbox attachment <dominio> <conversación> <mensaje> <índice> [-o <archivo>]
mailmask canned list <dominio> [--json]
mailmask canned create <dominio> --title <título> --body <texto> [--json]
mailmask canned delete <dominio> <id> [--yes] [--json]
mailmask signature get <dominio> [--json]
mailmask signature set <dominio> <markdown> [--json]
inbox get siempre imprime texto (un mensaje que sólo trae HTML sale sin etiquetas; con --json cada mensaje suma text) y marca la conversación como leída, igual que en la web. inbox reply y inbox compose envían correo de verdad: preguntan [y/N] y exigen --yes fuera de una terminal; compose pide el dominio activado y gasta cuota de envío. inbox delete acepta de 1 a 200 conversaciones y las manda a la papelera (inbox restore las saca). inbox attachment escribe a -o o a stdout, y si el adjunto no existe sale con 4 sin crear el archivo. signature set "" borra la firma (máx. 2000 caracteres).
Direcciones y buzones
mailmask aliases list <dominio> [--json]
mailmask aliases create <dominio> <alias> [destino...] [--mailbox] [--json]
mailmask aliases update <dominio> <alias> [destino...] [--enable | --disable] [--json]
mailmask aliases delete <dominio> <alias> [--yes] [--json]
mailmask aliases mailbox create <dominio> <alias> [--json]
mailmask aliases mailbox delete <dominio> <alias> [--yes] [--json]
mailmask aliases mailbox reset-password <dominio> <alias> [--yes] [--json]
mailmask aliases apple-profile <dominio> <alias> -o perfil.mobileconfig
mailmask aliases export <dominio> <alias> [-o buzon.mbox]
Webhooks, SMTP y API keys
mailmask webhooks list <dominio> [--json]
mailmask webhooks create <dominio> <url> --events email.received,email.bounced [--json]
mailmask webhooks update <dominio> <id> [--url ...] [--events ...] [--enable | --disable] [--json]
mailmask webhooks delete <dominio> <id> [--yes] [--json]
mailmask webhooks test <dominio> <id> [--json]
mailmask webhooks deliveries <dominio> <id> [--json]
mailmask smtp list <dominio> [--json]
mailmask smtp create <dominio> <etiqueta> [--json]
mailmask smtp revoke <dominio> <id> [--yes] [--json]
mailmask api-keys list [--json]
mailmask api-keys create <nombre> [--json]
mailmask api-keys revoke <id> [--yes] [--json]
El secreto sale completo sólo en create (secreto de firma del webhook, contraseña SMTP, API key) y con el aviso de que no se vuelve a mostrar; ningún otro comando lo imprime. Para tener otro, revoca y crea uno nuevo. webhooks create exige --events con email.received, email.sent, email.delivered, email.bounced o email.complained; un evento mal escrito sale con 1 sin tocar la red. webhooks y smtp piden el dominio activado. Si revocas la llave con la que estás conectado, api-keys revoke avisa que la sesión quedará sin llave y pide una segunda confirmación; con --json agrega activeKeyRevoked: true.
aliases create acepta uno o más destinos como argumentos sueltos (aliases create acme.com soporte ana@acme.com luis@acme.com); con --mailbox también se permite sin ningún destino, porque el correo se queda en el buzón IMAP. aliases update cambia destinos y/o el estado con --enable o --disable (no se combinan). El buzón IMAP vive bajo aliases mailbox y requiere el dominio activado.
La contraseña del buzón sale enmascarada (ab••••••yz) y no se vuelve a mostrar completa: para verla entera, mailbox reset-password con --json genera una nueva y la devuelve completa una sola vez; la anterior deja de servir. apple-profile escribe el perfil .mobileconfig de Apple Mail en la ruta de -o; export baja el buzón en mbox a un archivo con -o o por la salida estándar, para encadenarlo con otro comando.
Cuenta, equipo, cobros y dominios registrados
mailmask account profile [--name <texto>] [--json]
mailmask account avatar set <archivo> [--json]
mailmask account avatar remove [--yes] [--json]
mailmask members list <dominio> [--json]
mailmask members invite <dominio> --email <correo> --name <nombre> [--role admin|agent] [--json]
mailmask members remove <dominio> <miembroId> [--yes] [--json]
mailmask members cancel-invite <dominio> <token> [--yes] [--json]
mailmask billing status [--json]
mailmask billing addons [--json]
mailmask billing orders [--limit N] [--before <cursor>] [--json]
mailmask billing checkout <dominio> [--kind domain|storage50|sends100] [--period monthly|annual] [--payer-email <correo>] [--json]
mailmask billing cancel-addon <addonId> [--yes] [--json]
mailmask registrations search <dominio> [--json]
mailmask registrations tlds [--json]
mailmask registrations register <dominio> [--json]
mailmask registrations list [--json]
mailmask registrations renewal <dominio|registroId> [--payer-email <correo>] [--json]
mailmask registrations cancel-renewal <dominio|registroId> [--yes] [--json]
mailmask registrations transfer-out <dominio|registroId> [--yes] [--json]
mailmask transfers check <dominio> [--json]
mailmask transfers dns <dominio|registroId> [--json]
mailmask transfers set-dns <dominio|registroId> --file <registros.json> [--yes] [--json]
mailmask transfers approve-dns <dominio|registroId> [--yes] [--json]
mailmask transfers resend-email <dominio|registroId> [--json]
mailmask referrals get [--json]
mailmask referrals slug <slug> [--json]
mailmask referrals name <nombre> [--json]
El CLI nunca paga, nunca espera el pago y nunca pide, acepta ni muestra un código EPP. billing checkout, registrations register y registrations renewal sólo imprimen la liga de pago y terminan: la abres tú y nada cambia hasta que pagas. registrations transfer-out pide el traslado y el código llega por correo al dueño; el comando imprime el aviso, nunca el código.
registrations y transfers aceptan el nombre del dominio o el id de su registro (transfers check y registrations search/register reciben el nombre). transfers set-dns reemplaza el inventario completo con un arreglo JSON de { name, type, ttl?, values[] } que se valida antes de tocar la red; luego hay que aprobarlo con transfers approve-dns. account avatar set acepta .png, .jpg, .jpeg o .webp de hasta 2 MB y no tiene --url. account profile --name "" borra el nombre visible (máx. 60 caracteres). members invite imprime la liga de invitación.
Para scripts y agentes: --json y --yes
Con --json los comandos imprimen JSON, y un error sale por stderr como {"error": "...", "status": 404} (sin status si no vino de la API), para parsearlo sin adivinar el formato. Lo que borra o resetea — domains delete, domains logo remove, dns delete, rules delete, suppressions remove, inbox reply, inbox compose, inbox delete, canned delete, aliases delete, aliases mailbox delete y aliases mailbox reset-password, webhooks delete, smtp revoke api-keys revoke, members remove, members cancel-invite, billing cancel-addon, registrations cancel-renewal, registrations transfer-out, transfers set-dns, transfers approve-dns y account avatar remove — pregunta [y/N] en una terminal; fuera de una (CI, un agente) exige --yes y, sin él, sale con código 1 sin tocar la red.
Variable de entorno
MAILMASK_API_KEY tiene prioridad sobre la sesión guardada — es la vía recomendada para CI o para un agente de código.
Dónde se guarda la sesión
En el keychain del sistema operativo (Keychain en macOS, Secret Service/secret-tool en Linux). Si no hay keychain disponible —Windows, o un Linux sin Secret Service— cae a ~/.config/mailmask/credentials.json con permisos 600. mailmask logout la borra de donde haya quedado.
Códigos de salida
0— éxito.1— error genérico (también, un comando destructivo sin--yesfuera de una terminal).2— no hay API key activa, o MailMask la rechazó (401/403).3— conflicto (409), p. ej. el dominio ya existe.4— no encontrado (404).5— transitorio: 429, 5xx de MailMask o error de red. Vale la pena reintentar.
MCP Server
MailMask expone un servidor MCP (Model Context Protocol) en https://www.mailmask.studio/mcp, con transporte Streamable HTTP y sin sesiones. Un agente (Claude, ChatGPT, Ghosty Studio, Claude Code, Cursor, cualquier cliente MCP) puede dar de alta dominios, máscaras, buzones IMAP, reglas y webhooks, y enviar correo. Hay dos formas de conectarlo: con OAuth, sólo con la URL (la recomendada) o con una API key mk_.
Conectar con OAuth (sólo la URL, sin copiar llaves)
Pega la URL https://www.mailmask.studio/mcp en tu cliente y no pongas ningún encabezado. El cliente descubre solo el servidor de autorización (OAuth 2.1 con PKCE y registro dinámico de clientes), te abre MailMask en el navegador y, en cuanto das permiso, queda conectado.
- Ghosty Studio: Conectores → Mailmask → Conectar. Tu agente de Ghosty recibe las herramientas de MailMask sin que copies ninguna llave.
- Claude.ai y Claude Desktop: Settings → Connectors → Add custom connector, nombre
MailMasky la URL; luego Connect. - ChatGPT: Settings → Apps & Connectors → Create (requiere el modo desarrollador), con la URL y autenticación OAuth.
- Claude Code:
claude mcp add --transport http mailmask https://www.mailmask.studio/mcpy después/mcp→ mailmask → Authenticate. - Cualquier otro cliente que implemente la autorización de la especificación MCP: sólo la URL.
Qué ves tú: si no tienes sesión, MailMask te pide entrar (correo o Google); después aparece una pantalla con el nombre del cliente, la cuenta con la que entraste y a dónde te va a regresar, con los botones Permitir y Cancelar. Al permitir vuelves al cliente y la conexión queda lista.
Qué puede hacer esa conexión: lo mismo que una API key mk_ de tu cuenta (las 100 herramientas, 60 peticiones por minuto), salvo crear o listar API keys, entrar al panel de administración y crear o listar credenciales SMTP. Eso sólo lo haces tú desde el panel.
Tokens y cómo cortar el acceso: el cliente recibe un token de acceso mo_… que vence en una hora y uno de renovación mr_… que dura 60 días y rota en cada uso (si alguien reusa uno viejo, se revoca toda la conexión). Para dejar de usar una conexión, desconéctala desde el cliente (en Ghosty Studio, Conectores → Mailmask); los clientes que implementan revocación la cortan con POST /oauth/revoke. Para cortar todas al instante, cambia tu contraseña de MailMask: invalida cualquier token emitido antes.
Qué puede hacer y qué datos salen: el agente ve sólo lo que tú ves en tu cuenta. Cada herramienta declara si sólo lee (readOnlyHint) o si borra o reemplaza algo (destructiveHint), para que tu cliente te pida permiso antes de lo delicado; enviar correo y borrar siempre lo confirma contigo. Lo que el agente lee de tus correos viaja al proveedor del agente para que te responda: detalle en la política de privacidad, §9. Soporte: soporte@mailmask.studio.
Permiso sólo de DNS: un cliente OAuth puede pedir scope=dns en vez de mailmask. Ese token sólo lista tus dominios, ve uno, su DNS (/api/domains/:id/dns/*) y sus registros a pegar; no lee correo ni buzones y no entra al MCP. Para escribir varios registros de golpe: POST /api/domains/:id/dns/batch con {"changes":[{"action":"UPSERT","name":"www","type":"CNAME","values":["destino.example.com"]},{"action":"DELETE","name":"www","type":"A"}],"dryRun":false} (hasta 50; entran todos o ninguno, con las mismas reglas que un registro suelto). Cada paso regresa before y after para poder deshacerlo.
Apps de los directorios de Claude y ChatGPT: usan https://www.mailmask.studio/mcp/directory, el mismo servidor con un catálogo acotado: sin pagos, activaciones, compra, transferencia o renovación de dominios, pedidos ni referidos, y sin herramientas que devuelvan contraseñas o secretos (buzones IMAP nuevos, secretos de webhooks, credenciales SMTP). Eso se hace desde el panel. Mismo OAuth; su metadata vive en /.well-known/oauth-protected-resource/mcp/directory y su resource es esa URL. Un token sirve sólo en la URL con la que se conectó.
Para quien implementa un cliente: /mcp sin credencial responde 401 con WWW-Authenticate: Bearer resource_metadata="https://www.mailmask.studio/.well-known/oauth-protected-resource". Metadata del servidor en /.well-known/oauth-authorization-server; registro público en POST /oauth/register (redirect https o http a loopback); /oauth/authorize exige PKCE S256 y resource=https://www.mailmask.studio/mcp; POST /oauth/token (code y refresh_token) y POST /oauth/revoke (RFC 7009).
Alternativa: con una API key mk_
Si tu cliente no soporta OAuth, o es un script o servidor sin navegador, sigue funcionando la API key de la sección API Keys en el encabezado Authorization: Bearer mk_….
# Claude Code
claude mcp add --transport http mailmask https://www.mailmask.studio/mcp \
--header "Authorization: Bearer mk_..."
// Cursor y otros clientes (mcp.json)
{
"mcpServers": {
"mailmask": {
"type": "http",
"url": "https://www.mailmask.studio/mcp",
"headers": { "Authorization": "Bearer mk_..." }
}
}
}
Herramientas
Son 100 herramientas, que cubren lo mismo que el panel; cada una es un método del SDK, con las mismas reglas y límites. Al conectarse, el cliente recibe además una guía (instructions) con el orden para conectar un dominio, qué significa gratis, activado y bloqueado, y cómo funcionan los pagos.
- Dominios:
list_domains,get_domain,create_domain(devuelve los registros DNS: MX, TXT, DKIM, SPF),domain_dns_setup(los registros exactos para pegar en tu registrador, cuáles ya se ven y en qué panel van),verify_domain,domain_health,delete_domain. El estado de un dominio (MX, verificación) se confirma condomain_health, que revisa en vivo;list_domainses inventario: sus banderas traencheckedAty, si la revisión tiene más de 24 h, salenunknown. - Direcciones (máscaras) y buzones:
list_aliases,create_alias(conmailbox: truecrea también el buzón IMAP),update_alias,delete_alias,create_mailbox,delete_mailbox,reset_mailbox_password(contraseña nueva, devuelta una sola vez),apple_profile_link(liga al perfil de Apple Mail),mailbox_export_link(liga para descargar el.mbox). - DNS:
list_dns_records,create_dns_zone,dns_delegation_status,set_dns_record,delete_dns_record,import_dns_records,point_domain_to(apunta el dominio a Vercel, Netlify, GitHub Pages, Cloudflare Pages, Render, Fly o EasyBits sin saber qué registros hacen falta; condryRunsólo muestra el cambio),website_status(a dónde apunta hoy la raíz, www y cada subdominio, la plataforma detectada y si responde por HTTPS). - Sitio de EasyBits (con tu cuenta de EasyBits conectada en la pestaña Sitio web):
easybits_site_status(qué sitio está publicado y los sitios de tu cuenta),publish_easybits_site(lo da de alta en EasyBits, escribe sus registros sin tocar el correo, verifica y asigna la raíz; condryRunsólo muestra el cambio),unpublish_easybits_site(deja el DNS como estaba antes de publicar —lo que cambiaste a mano después no se toca— y limpia EasyBits). Sin cuenta conectada responden 409 con la liga para conectarla:/app?domain=tudominio.com&easybits=1. - Activación y cobro:
activation_link(liga de MercadoPago para activar el dominio, mensual o anual, o sumarle un bloque),list_addons,billing_status. - Comprar y renovar dominios:
search_domains,domain_prices,register_domain,list_registrations,renewal_status,renewal_link. - Transferencias:
transfer_check,transfer_start(devuelve la liga al formulario seguro donde se pega el código EPP: el código nunca pasa por el chat),transfer_status,transfer_dns,update_transfer_dns,approve_transfer_dns,resend_transfer_email,transfer_out(el EPP llega por correo al dueño). - Equipo:
list_members,invite_member,remove_member,cancel_invite. - Bandeja (el agente atiende el correo de su máscara):
inbox_list(conversaciones por máscara, estado, no leídas, búsquedaqy paginación),inbox_read(mensajes en texto, notas y adjuntos; la marca leída),inbox_attachment(texto o imagen de un adjunto),inbox_reply(contesta en el hilo desde la máscara del hilo; no gasta envíos),inbox_send(correo nuevo que abre hilo; dominio activado),inbox_mark(leída, cerrada, pospuesta, prioridad, etiquetas),inbox_assign,inbox_note(nota interna),inbox_delete,inbox_restore,inbox_metrics,upload_attachment. - Firma y respuestas guardadas:
get_signature,set_signature,set_domain_logo,delete_domain_logo,list_canned_replies,create_canned_reply,delete_canned_reply. - Perfil de la cuenta (tu usuario de MailMask, no una máscara):
get_profile,update_profile(nombre visible, máx. 60),set_profile_photo(sólo con una imagen que adjuntaste en el chat del asistente),delete_profile_photo,list_orders(historial de cobros),cancel_addon,cancel_renewal,referral_status,set_referral_slug,set_referral_name,export_link. - Reglas:
list_rules,create_rule,update_rule,delete_rule. - Webhooks:
list_webhooks,create_webhook,update_webhook,delete_webhook,test_webhook,webhook_deliveries. - Envío:
send_email(conidempotencyKey),bulk_send,bulk_status. - Operación:
list_logs,list_suppressions,add_suppression,remove_suppression,list_smtp_credentials,create_smtp_credential,revoke_smtp_credential. search_tools: busca herramientas por palabra clave (sin acentos: «renovacion» encuentrarenewal_link), para clientes que cargan el catálogo bajo demanda.
Las ligas de pago (activation_link, register_domain, renewal_link) las abre y paga una persona; la herramienta devuelve paid: false y el agente debe confirmarlo después con list_addons o list_registrations.
Sobre el DNS: set_dns_record reemplaza el conjunto de valores de ese nombre y tipo, así que para añadir un valor hay que leer primero con list_dns_records e incluir también los que ya estaban. Los registros que MailMask necesita para tu correo (el MX, el TXT de verificación, el SPF y los CNAME de DKIM) vienen marcados con managed y no se pueden borrar: el agente recibe un 409 explicando por qué.
Sobre la Bandeja: cada herramienta pasa por los mismos permisos que la app (dueño, admin o agente invitado al dominio) y una conexión sólo ve los dominios de su cuenta. inbox_reply sale siempre desde la máscara del hilo; inbox_send pide dominio activado y verificado y gasta un envío del día. Las descripciones de las que envían le piden al agente confirmar el texto contigo antes. Así puedes darle a tu agente su propio correo (una máscara con Bandeja) en vez de acceso a tu Gmail.
Un error del servidor (por ejemplo, un dominio gratis pidiendo un buzón) llega al agente como resultado con isError y el mensaje tal cual, no como fallo del protocolo. Las API keys no se crean ni revocan por MCP: un agente con una llave no puede fabricar más. El límite es el mismo de la API: 60 peticiones por minuto por llave o por conexión OAuth.
Acciones que piden confirmación
Cuando quien llama es Mask, el asistente de la app, no lleva tu API key sino un token de turno (mt_…) que dura 5 minutos. Con ese token, lo irreversible no se ejecuta: borrar un dominio, una máscara, un buzón o un registro DNS, sacar a un miembro, transferir un dominio fuera y cancelar un add-on o la renovación de un dominio. La ruta guarda la acción y responde 409 con {"error": "needs_confirmation", "actionId", "summary"}; por MCP llega como resultado normal con needsConfirmation: true, no como error, para que el agente no reintente. A ti te aparece una tarjeta en el asistente, armada con los datos reales de la base (no con el texto del modelo), y la acción sólo corre cuando la apruebas con tu sesión. Si no la apruebas en 15 minutos, caduca. Con una API key mk_ estas acciones se ejecutan directo, como siempre.
Referencia API
Para la referencia completa de la API HTTP (todos los endpoints, parámetros y respuestas), consulta la documentación interactiva:
© MailMask — mailmask.studio