Un ingeniero nuevo empieza el lunes. Pegas el enlace para unirse en el canal de incorporación, hace clic y ya está dentro: sin idas y vueltas de correos, sin esperar un correo de verificación que acabó en spam.
Cuatro meses después, ese canal sigue existiendo, y el enlace también. Y sigue funcionando. Funciona para el colaborador externo al que añadieron al canal la sexta semana, para cualquiera a quien él se lo reenviara y para quien acabe abriendo el archivo exportado del canal.
Un enlace de invitación es una credencial al portador. No sabe quién eres, solo que lo tienes. Ahí está todo su atractivo y todo su problema. Esto es lo que tiene que llevar un enlace antes de que sea seguro pegarlo en cualquier sitio, lo que lancé en Kookee y las partes que resultaron más difíciles de lo que parecían.
Una invitación nombra a una persona; un enlace, no
Una invitación por correo tiene destinatario. El sistema ya sabe a quién deja entrar —a la dirección a la que envió el correo— y el token de ese correo no le sirve a nadie más. Es precisa, y es lenta: alguien tiene que escribir direcciones, el correo tiene que llegar y el destinatario tiene que encontrarlo.
Un enlace es justo lo contrario. Es instantáneo y no tiene destinatario. Entra quien lo abre.
La mayoría de los equipos quieren las dos cosas, por la misma razón por la que quieren una lista de invitados y también una puerta. El error es construir el enlace como si fuera la invitación sin la parte de escribir direcciones.
Cuatro preguntas que un enlace tiene que responder
¿Quién puede usarlo? Sin una respuesta, la descripción honesta de la función es «cualquiera en internet que vea esta cadena entra en tu proyecto». Un enlace que se puede limitar a los dominios que de verdad usan las direcciones de tus compañeros es un objeto distinto de uno que no.
¿Cuánto tiempo sigue abierto? Todo enlace que nunca caduca acaba siendo un enlace que nadie recuerda haber creado. El valor por defecto importa más que la opción: si «nunca» es el valor por defecto, «nunca» es lo que tendrán la mayoría de los enlaces.
¿Quién puede retirarlo? Revocarlo tiene que costar un clic, y la lista de enlaces activos tiene que estar donde alguien vaya a mirar. Una credencial que no puedes listar es una credencial que no puedes revocar.
¿Qué concede? La respuesta debería ser el rol útil más bajo, sin excepciones. Un enlace que puede dar permisos de administrador es un enlace que puede dar permiso para crear más enlaces.
Una regla de dominio no vale nada sin una dirección verificada
La forma obvia de limitar un enlace es restringirlo a un dominio de correo. Lo que no es tan obvio es que la restricción solo significa algo si la dirección está verificada.
if (link.allowedDomains?.length) {
const allowed = link.allowedDomains.includes(domainOf(user.email));
if (!user.emailVerified || !allowed) throw Forbidden('INVITE_LINK_DOMAIN_MISMATCH');
}
Quita la comprobación de emailVerified y la función se vuelve del revés: cualquiera que abra el enlace se registra como someone@yourcompany.com, nunca abre el correo de confirmación y entra. La regla de dominio pasaría a ser una pista sobre quién esperabas que se uniera.
Debajo hay dos decisiones más pequeñas.
Los dominios de la lista los compara una máquina, no los lee una persona. Una «а» cirílica es un carácter distinto de una «a» latina y se ve idéntica, así que un homoglifo en una lista de dominios permitidos es una puerta abierta que parece cerrada. Los dominios admiten letras ASCII, dígitos y guiones, y un dominio internacionalizado se introduce en su forma punycode:
// Letras ASCII, dígitos y guiones, con TLD. Los dominios IDN se introducen como xn--…
const DOMAIN = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
El dominio que guardas tiene que ser el dominio que comparas. Kookee normaliza las direcciones al recibirlas, y googlemail.com es el mismo buzón que gmail.com. Una regla escrita como googlemail.com no coincidiría nunca con nadie: parece correcta, se guarda sin quejas y rechaza a todo el mundo sin decir nada. Normalizarla al crear la regla cuesta tres líneas, y evita un hilo de soporte imposible de diagnosticar desde fuera.
Un enlace sobrevive a la persona que lo creó
Un administrador crea un enlace en marzo. En junio se cambia a otro equipo y lo bajan a miembro, o se va y lo eliminan, o lo banean. El enlace sigue en el canal.
Hay dos diseños defendibles. Comprobar al creador cada vez que alguien se une —leer su rol actual y rechazar la incorporación si ya no lo tiene— o revocar el enlace cuando ocurre el cambio. El primero parece más correcto y es peor: añade una consulta a cada incorporación y obliga a responder preguntas incómodas sobre qué significa el enlace de un propietario después de que el proyecto cambie de manos.
Elegí revocar en el momento del cambio. Eliminar a un miembro, quitarle el rol de propietario o de administrador, o banearlo revoca todos los enlaces que creó, en la misma transacción de base de datos que el propio cambio de membresía. La regla que hay detrás se dice fácil en voz alta: un enlace es una extensión de la autoridad de quien lo creó, y termina cuando termina esa autoridad.
Las plazas son la otra mitad de la pregunta
Un enlace que cualquiera puede reenviar, para un plan con un número fijo de plazas, también es una forma de gastar el dinero de otro. Por eso la comprobación de plazas se hace al unirse, no solo al invitar:
const seatsInUse = members + pendingInvitations;
if (seatsInUse + 1 > plan.members.maxPerProject) throw Conflict('MEMBER_LIMIT_REACHED');
Contar las invitaciones pendientes es la parte que se olvida con facilidad. Sin eso, tres plazas absorben diez invitaciones, todos reciben un correo de bienvenida y los últimos siete reciben un error al aceptar: el peor momento posible para decirle a alguien que el equipo está completo.
Contarlas dos veces es el error contrario. Si a una persona ya la invitaron por correo y luego se une con un enlace, su plaza ya está reservada; cobrarle una segunda rechaza una incorporación que a todas luces está bien. Su propia invitación pendiente queda fuera del recuento, y la incorporación la cierra.
Lo que lancé en Kookee
Los propietarios y administradores tienen una sección Enlaces de invitación en la página del equipo del proyecto. Cada enlace activo aparece con lo que permite y cuándo caduca, y a su lado un botón para copiarlo y otro para revocarlo.

Crear uno son dos decisiones: qué dominios de correo pueden usarlo y cuánto tiempo sigue abierto. Si dejas los dominios vacíos, cualquier dirección puede unirse; si los rellenas, el enlace solo admite direcciones verificadas de esos dominios. El enlace caduca a los 7 días, a los 30 o nunca; si no eliges, a los 30.

Quien abre el enlace ve el proyecto al que se une y la restricción, si la hay. Tiene que haber iniciado sesión con una cuenta real —una sesión de invitado no puede ocupar una plaza— y siempre entra como miembro, nunca como administrador.

Un límite que descubres al chocar con él es un informe de error
Las plazas son uno de varios límites que un plan fija en un proyecto, y el mismo razonamiento vale para todos: un número que solo conoces al chocar con él te llega como un mensaje enfadado y no como una mejora de plan.
Por eso cada proyecto muestra ahora lo que permite su plan junto a lo que está usando —plazas, entradas, claves de API, categorías, etiquetas, tipos de entrada y configuraciones— y cada lista avisa antes de llenarse, no después.

Si estás construyendo enlaces de invitación
Trata el enlace como una credencial al portador, porque eso es lo que es. Diséñalo como diseñarías una contraseña compartida, no como diseñarías un correo.
Limítalo a dominios verificados y rechaza sin más las direcciones sin verificar. Una dirección sin verificar convierte la regla de dominio en decoración.
Mantén en ASCII los dominios de la lista, y normalízalos igual que normalizas las direcciones con las que los comparas.
Ponle caducidad por defecto, y haz que «nunca» sea la opción deliberada, no la fácil.
Concede el rol útil más bajo. Un enlace nunca debería poder crear más enlaces.
Revoca un enlace cuando quien lo creó pierde la autoridad con la que lo creó.
Cuenta las invitaciones pendientes en tu límite de plazas, y cuenta a cada persona una sola vez.
Muestra todos los enlaces activos donde alguien vaya a mirar, y que revocar uno sea un solo clic.
Los proyectos de Kookee ya tienen enlaces de invitación para compartir, y cada proyecto muestra los límites que fija su plan. Consulta Invitar a tu equipo a un proyecto para crear uno, o Límites de peticiones, planes y gestión de errores para ver qué permite cada plan.