El correo de confirmación es la parte de un flujo de registro que una prueba de navegador no puede recorrer por su cuenta. Algo tiene que poseer una dirección, recibir el mensaje y devolver el código a la prueba. Una API de buzón temporal hace justo eso: la suite pide a un servicio que cree un buzón por HTTP, lee lo que llega y descarta el buzón cuando el caso termina. No hay navegador, no hay buzón compartido entre personas y no hay copia manual.
Este artículo trata del lado de la API de ese montaje. No va de apuntar la aplicación a un endpoint local de recepción: eso es cómo capturar correo en CI, y ambos resuelven problemas distintos. La captura local demuestra lo que envía tu aplicación. La API de buzón demuestra lo que de verdad llega, por una ruta real de entrega, a una dirección que la prueba controla.
¿Por qué usar una API de buzón en las pruebas?
Porque la alternativa es un buzón humano de verdad o nada.
Un buzón compartido es un fixture pobre. Varias ejecuciones leen el mismo buzón, los mensajes de una ejecución anterior siguen ahí y la dirección acumula tráfico que ninguna prueba pidió. Entonces cada aserción tiene que adivinar qué mensaje pertenece al caso actual, y en la adivinanza nace la intermitencia.
Recolectar la interfaz web de un proveedor con un navegador es la segunda mala opción. Hace que la prueba dependa de un marcado que cambia sin aviso, del estado de sesión y de un inicio de sesión que la suite debe vigilar. En cuanto el proveedor cambia el estilo de un botón, una suite verde se pone roja por un motivo sin relación con el producto.
La API de buzón elimina ambos problemas. La dirección se crea para el caso, está vacía por construcción y se lee mediante una interfaz estable que la prueba puede llamar directamente. A la suite deja de importarle cómo se ve el proveedor y solo le importa que se cumpla el contrato: crear, recibir, leer, borrar.
También hay un argumento de privacidad. Una dirección creada por API no representa a nadie. No es el buzón de una persona, no es un lugar donde pudiera caer un mensaje real y se tira con la ejecución.
¿Qué endpoints necesita realmente una prueba?
Una API de buzón puede exponer decenas de rutas, pero un cliente de pruebas necesita cuatro operaciones, y ayuda nombrarlas tal como las usará la suite.
Crear devuelve una dirección y un handle. La dirección es lo que se le dice a la aplicación bajo prueba como destino. El handle, a menudo un token o identificador, es lo que usa la prueba para preguntar por ese buzón en cada llamada posterior. La suite debe tratar el par como un solo objeto y no reconstruir nunca el handle a partir de la dirección, porque los proveedores son libres de hacer que ambos no tengan relación.
Listar devuelve resúmenes en vez de cuerpos: una entrada por mensaje, con identificador, remitente, asunto y hora de llegada. Es la llamada que debe usar un bucle de sondeo, porque es barata y basta para responder a la única pregunta que importa al principio: si ya ha llegado algo.
Leer devuelve un mensaje completo, incluidas las partes de texto y de HTML. Ahí vive el código, y es la llamada que solo debe hacerse después de que listar haya reportado una coincidencia.
Limpiar elimina los mensajes del buzón o borra el buzón entero. La prueba lo necesita por dos razones: reiniciar entre intentos sin crear una dirección nueva y limpiar cuando el caso termina.
Algunos servicios añaden un endpoint de espera o de sondeo largo, que mantiene la conexión hasta que llega un mensaje o vence un tiempo límite. Es cómodo, pero el cliente debería poder recurrir a listar, porque la llamada de espera es la parte más propensa a sufrir límites de tasa.
¿Cómo sondear el código sin intermitencias?
El error más común en este tipo de prueba es la espera fija. Un número constante de segundos es una conjetura: demasiado corta cuando la entrega es lenta, un derroche cuando es rápida, y equivocada en ambos sentidos en una máquina de CI cargada. Sustitúyela por un bucle que llama a listar, comprueba una coincidencia y vuelve en cuanto la encuentra, con un techo que hace fallar la prueba en lugar de colgar el trabajo.
La coincidencia es la otra mitad del problema. El mensaje que la prueba quiere es el dirigido a la dirección que creó el caso y, si el buzón puede guardar más de un tipo de correo, el que lleva en el asunto un fragmento estable. Prefiere la coincidencia más reciente, para que una entrega duplicada de un reintento no confunda la lectura. No tomes nunca el primer mensaje sin criterio; en una dirección reutilizada, así es exactamente como acaba validándose un código viejo.
La extracción debe estar anclada. Un cuerpo puede contener un número de referencia, una marca de tiempo y un precio, y un analizador que toma la primera racha de dígitos a veces toma uno de esos en lugar del código. Busca el texto que introduce el código, lee el código en su entorno y, cuando no coincida nada, falla con el cuerpo adjunto.
Por último, respeta el límite de reenvío. Un flujo de código suele permitir solo unos pocos envíos en una ventana corta, y ese límite forma parte del comportamiento bajo prueba. Una prueba que pulsa el botón otra vez para conseguir un código nuevo acabará rechazada y fallará por el motivo equivocado. Reintenta leyendo el buzón de nuevo, no disparando otro mensaje.
¿Cómo integrarla en una suite de extremo a extremo o de CI?
La forma limpia es un fixture. Antes de que empiece el flujo, el fixture crea un buzón y devuelve su dirección. La prueba conduce la aplicación usando esa dirección. Cuando la aplicación confirma que ha enviado algo, la aserción lee el buzón y extrae el código. Al terminar el caso, el fixture borra el buzón.
Mantén el cliente pequeño e inyectable. Un módulo envuelve las cuatro llamadas; la prueba depende de ese módulo, nunca de HTTP crudo repartido por la suite. Eso permite cambiar por un doble en las pruebas unitarias y apuntar la misma suite a otro proveedor sin reescribir aserciones.
En CI, las credenciales van en el almacén de secretos del trabajo, nunca en el repositorio ni en una línea de registro. Da a cada trabajo o a cada worker en paralelo su propio buzón, y pon a las direcciones generadas un prefijo que identifique la ejecución, para que un mensaje perdido pueda atribuirse por inspección. Fija el timeout del cliente por debajo del timeout del propio trabajo, para que un sondeo atascado falle con un mensaje claro en lugar de una cancelación abrupta.
Reintenta la lectura, no el flujo entero. Si el código aún no ha llegado, espera y lee otra vez; rehacer el registro produciría un segundo mensaje y, con él, un segundo candidato para la aserción. Y mantén la API de buzón fuera de los flujos de producción: es infraestructura de prueba, y una suite nunca debería poder enviar a un cliente real desde ella.
El flujo en torno al código, y no la mecánica de leerlo, se cubre en pruebas del flujo de verificación de correo, y el paso de análisis es el tema de OTP en pruebas de extremo a extremo.
Aislamiento y limpieza
Una dirección por caso es la regla que evita la mayoría de los fallos entre pruebas. Elimina la necesidad de razonar sobre qué mensaje pertenece a quién y hace desaparecer la cuestión de la frescura, porque el buzón solo ha recibido el tráfico de un caso.
La limpieza debe ser explícita e incondicional. Borra el buzón en un teardown que se ejecute tanto si el caso pasa como si falla, no solo en el camino feliz. Confiar solo en el tiempo de vida del proveedor es un error: el mensaje puede quedarse el tiempo suficiente para que lo lea una ejecución posterior en la misma máquina, y ese tiempo de vida es una comodidad, no una garantía.
Si la limpieza falla, regístralo y deja que la suite termine. Un error de limpieza merece conocerse, pero no es lo mismo que un defecto de producto, y hacer fallar la ejecución por él enseña al equipo a ignorar los fallos de teardown. Trata todo lo que recibió la dirección como datos de prueba: existe para una aserción, no debe exportarse ni compartirse y nunca debe tratarse como el punto de contacto de nadie.
Límites y advertencias
La API de buzón sigue siendo una dependencia de terceros, y sus límites se convierten en los tuyos. Los límites por minuto pueden rechazar una ráfaga de creaciones de una ejecución paralela grande. Las cuotas limitan cuántas direcciones existen a la vez. Los mensajes pueden retrasarse, y un mensaje retrasado se ve exactamente como uno perdido hasta que llega.
Los dominios desechables también están ampliamente bloqueados. El dominio de un proveedor puede ser rechazado por el propio formulario de registro que estás probando, lo que convierte una prueba legítima en un fallo confuso. Cuando eso ocurre, la respuesta no es hacer una excepción con el proveedor, sino entender si el producto bajo prueba rechaza a propósito las direcciones desechables y probar ese comportamiento de forma deliberada.
El resumen honesto es que la API de buzón es la herramienta correcta para afirmar lo que llega. Para afirmar lo que emite tu aplicación, un endpoint local de recepción es más rápido y no tiene cuota. La mayoría de las suites maduras usan ambos: captura local para el grueso de las aserciones y API de buzón solo donde la ruta real de entrega es el objeto de la prueba.
Próximos pasos
Busca una prueba que lea códigos sondeando un buzón compartido y cámbiala por un fixture que cree una dirección nueva desde una API de buzón. Registra la dirección junto con la ejecución, bórrala en el teardown y observa cuánta de la intermitencia que venías tolerando deja de ocurrir sin más. Cuando necesites un buzón real a mano, la página de correo temporal crea uno al momento, y las direcciones que entrega son andamiaje para una ejecución, nunca una identidad real.