Replace Resend with n8n webhook relay for emails
- email.ts: POST to N8N_EMAIL_WEBHOOK_URL instead of Resend API - env.ts: replace RESEND_API_KEY with N8N_EMAIL_WEBHOOK_URL - .env.example: document new env var - Add n8n workflow JSON (Webhook -> Gmail -> Respond) ready to import
This commit is contained in:
@@ -12,9 +12,9 @@ NODE_ENV=development
|
|||||||
SUPABASE_URL=https://tu-supabase.tudominio.com
|
SUPABASE_URL=https://tu-supabase.tudominio.com
|
||||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||||
|
|
||||||
# Resend — servicio de email
|
# n8n — relé de email vía webhook (n8n se encarga de enviar por Gmail/SMTP)
|
||||||
# Crear cuenta en https://resend.com y configurar dominio
|
# La URL es la del webhook configurado en el workflow de n8n.
|
||||||
RESEND_API_KEY=re_...
|
N8N_EMAIL_WEBHOOK_URL=https://n8n.tudominio.com/webhook/naturcalabacera-email
|
||||||
EMAIL_FROM=Naturcalabacera <reservas@tudominio.com>
|
EMAIL_FROM=Naturcalabacera <reservas@tudominio.com>
|
||||||
|
|
||||||
# Destinatarios de notificaciones (parametrizables, sin hardcodear)
|
# Destinatarios de notificaciones (parametrizables, sin hardcodear)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export const env = {
|
|||||||
SUPABASE_URL: required('SUPABASE_URL'),
|
SUPABASE_URL: required('SUPABASE_URL'),
|
||||||
SUPABASE_SERVICE_ROLE_KEY: required('SUPABASE_SERVICE_ROLE_KEY'),
|
SUPABASE_SERVICE_ROLE_KEY: required('SUPABASE_SERVICE_ROLE_KEY'),
|
||||||
|
|
||||||
// Email — Resend
|
// Email — n8n webhook (relé a Gmail)
|
||||||
RESEND_API_KEY: required('RESEND_API_KEY'),
|
N8N_EMAIL_WEBHOOK_URL: required('N8N_EMAIL_WEBHOOK_URL'),
|
||||||
EMAIL_FROM: optional('EMAIL_FROM', 'Naturcalabacera <reservas@naturcalabacera.com>'),
|
EMAIL_FROM: optional('EMAIL_FROM', 'Naturcalabacera <reservas@naturcalabacera.com>'),
|
||||||
|
|
||||||
// Destinatarios de notificaciones (configurables, sin hardcodear)
|
// Destinatarios de notificaciones (configurables, sin hardcodear)
|
||||||
|
|||||||
@@ -7,40 +7,34 @@ interface SendEmailOptions {
|
|||||||
replyTo?: string;
|
replyTo?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResendResponse {
|
|
||||||
id?: string;
|
|
||||||
error?: { message: string; name: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envía un email usando la API de Resend.
|
* Envía un email a través del webhook de n8n.
|
||||||
* Docs: https://resend.com/docs/api-reference/emails/send-email
|
* El workflow de n8n recibe { to, subject, html } y lo envía vía Gmail.
|
||||||
*/
|
*/
|
||||||
export async function sendEmail(options: SendEmailOptions): Promise<{ success: boolean; id?: string; error?: string }> {
|
export async function sendEmail(options: SendEmailOptions): Promise<{ success: boolean; id?: string; error?: string }> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://api.resend.com/emails', {
|
const to = Array.isArray(options.to) ? options.to.join(', ') : options.to;
|
||||||
|
|
||||||
|
const res = await fetch(env.N8N_EMAIL_WEBHOOK_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
from: env.EMAIL_FROM,
|
to,
|
||||||
to: Array.isArray(options.to) ? options.to : [options.to],
|
|
||||||
subject: options.subject,
|
subject: options.subject,
|
||||||
html: options.html,
|
html: options.html,
|
||||||
...(options.replyTo && { reply_to: options.replyTo }),
|
from: env.EMAIL_FROM,
|
||||||
|
replyTo: options.replyTo,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json() as ResendResponse;
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => '');
|
||||||
if (!res.ok || data.error) {
|
const errorMsg = `HTTP ${res.status}${text ? ` — ${text.slice(0, 200)}` : ''}`;
|
||||||
const errorMsg = data.error?.message ?? `HTTP ${res.status}`;
|
|
||||||
console.error('[email] Error al enviar:', errorMsg);
|
console.error('[email] Error al enviar:', errorMsg);
|
||||||
return { success: false, error: errorMsg };
|
return { success: false, error: errorMsg };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = await res.json().catch(() => ({})) as { id?: string };
|
||||||
return { success: true, id: data.id };
|
return { success: true, id: data.id };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = err instanceof Error ? err.message : 'Error desconocido';
|
const errorMsg = err instanceof Error ? err.message : 'Error desconocido';
|
||||||
|
|||||||
71
n8n-workflows/naturcalabacera-email-relay.json
Normal file
71
n8n-workflows/naturcalabacera-email-relay.json
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "Naturcalabacera - Email Relay",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "naturcalabacera-email",
|
||||||
|
"responseMode": "responseNode",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "webhook-naturcalabacera",
|
||||||
|
"name": "Webhook (POST email)",
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [240, 300]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"sendTo": "={{ $json.body.to }}",
|
||||||
|
"subject": "={{ $json.body.subject }}",
|
||||||
|
"emailType": "html",
|
||||||
|
"message": "={{ $json.body.html }}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "gmail-send",
|
||||||
|
"name": "Gmail — Enviar",
|
||||||
|
"type": "n8n-nodes-base.gmail",
|
||||||
|
"typeVersion": 2.1,
|
||||||
|
"position": [520, 300]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "json",
|
||||||
|
"responseBody": "={{ { success: true, id: $json.id } }}",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "respond-ok",
|
||||||
|
"name": "Responder OK",
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [800, 300]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"Webhook (POST email)": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Gmail — Enviar",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Gmail — Enviar": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Responder OK",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user