@mafesoftware/correo
v0.2.1
Published
Envío de mails transaccionales por Resend para los productos de MAFE Software. Sin dependencias, sin framework: fetch inyectable, resultados en vez de excepciones y una plantilla HTML con la marca de cada producto.
Maintainers
Readme
@mafesoftware/correo
Envío de mails transaccionales por Resend para los
productos de MAFE Software (store360, consult360, …). Sin dependencias, sin
framework: fetch inyectable para los tests y resultados en vez de
excepciones — un mail es un aviso, y mandarlo no puede tumbar la operación
que lo dispara.
import { enviarCorreo, plantillaCorreo, botonCorreo, escapeHtml } from "@mafesoftware/correo";
const r = await enviarCorreo({
apiKey: process.env.RESEND_API_KEY!,
de: "Bestie K-Beauty <[email protected]>",
para: "[email protected]",
asunto: "¡Gracias por tu compra!",
html: plantillaCorreo({
marca: { nombre: "Bestie K-Beauty", colorFondo: "#5c1f30" },
cuerpoHtml: `<h1>Pedido W-0012</h1>${botonCorreo("https://bestie.com.ar/cuenta", "Ver mi pedido", "#5c1f30")}`,
}),
});
if (!r.ok) console.error(r.categoria, r.error);API
enviarCorreo(opciones)— POST a la API de Resend. Nunca tira: devuelve{ ok: true, id }o{ ok: false, categoria, error }. La categoría dice si reintentar sirve:red,limiteyconflicto_idempotenciasí;credencialesyrechazadono. Soporta varios destinatarios,responderA(reply-to) y adjuntos (los bytes se codifican a base64 acá).claveIdempotencia(opcional) se manda como headerIdempotency-Key: con la misma clave, un segundo envío no duplica el mail — Resend devuelve el resultado del primero (o, si el cuerpo del segundo envío es DISTINTO al del primero con la misma clave, HTTP 409 →categoria: "conflicto_idempotencia").señal(opcional,AbortSignal) se pasa tal cual alfetch— cancelarla corta el pedido, aunque no deshace un envío que Resend ya haya aceptado.const controlador = new AbortController(); await enviarCorreo({ ...opciones, claveIdempotencia: "tenant-1:confirmacion-pedido-42", señal: controlador.signal, });Ejemplo completo arriba.
plantillaCorreo({ marca, cuerpoHtml, pie })— la cáscara HTML con la marca del producto: encabezado con color propio, ancho fijo, estilos en línea (los clientes de correo ignoran<style>). ElcuerpoHtmllo arma la aplicación; la marca y el pie se escapan acá. Ejemplo arriba.botonCorreo(url, texto, color?)— un<a>con estilo de botón, listo para meter dentro de uncuerpoHtml:botonCorreo("https://bestie.com.ar/cuenta", "Ver mi pedido", "#5c1f30"); // '<p style="..."><a href="https://bestie.com.ar/cuenta" style="...">Ver mi pedido</a></p>'escapeHtml(texto)— escapa&,<,>,"y'antes de interpolar texto de usuario en HTML:escapeHtml('<b>hola</b> & "chau"'); // '<b>hola</b> & "chau"'
Reglas
- Todo texto que venga de un usuario pasa por
escapeHtmlantes de entrar al cuerpo. La plantilla escapa lo suyo (marca, pie); delcuerpoHtmles responsable quien lo arma. - El remitente tiene que ser de un dominio verificado en Resend.
- En los tests, inyectá
fetch: la suite no tiene por qué hablar con Resend.
