Automatizarea emailului ramane un pilon al operatiunilor de business, de la comunicarile cu clientii pana la notificarile interne. Acest ghid exploreaza construirea de workflow-uri email pregatite pentru productie cu n8n, care gestioneaza scenarii complexe precum renderizarea template-urilor, procesarea atasamentelor si fallback-uri multi-canal.
Configurarea nodului de email
Configurare integrare SMTP
Configureaza trimiterea SMTP cu autentificare corecta:
// n8n SMTP Credentials Configuration
{
"name": "Production SMTP",
"type": "smtp",
"data": {
"host": "smtp.sendgrid.net",
"port": 587,
"secure": false,
"user": "apikey",
"password": "={{ $credentials.sendgridApiKey }}"
}
}Trigger IMAP pentru email
Monitorizeaza inbox-ul pentru emailuri primite:
// IMAP Email Trigger Node Configuration
{
"parameters": {
"mailbox": "INBOX",
"postProcessAction": "markAsRead",
"options": {
"customEmailRules": [
{
"key": "from",
"value": "*@example.com"
}
],
"downloadAttachments": true,
"forceReconnect": true
}
},
"credentials": {
"imap": {
"id": "imap-credentials-id",
"name": "Support Inbox"
}
}
}Implementarea motorului de template-uri
Template-uri dinamice de email
Creeaza template-uri reutilizabile cu substitutie de variabile:
// Function Node: Template Renderer
const templates = {
welcome: {
subject: "Welcome to {{company}}, {{firstName}}!",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h1 style="color: #2563eb;">Welcome, {{firstName}}!</h1>
<p>Thank you for joining {{company}}. We're excited to have you on board.</p>
<div style="background: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0;">
<h3>Getting Started</h3>
<ul>
{{#each onboardingSteps}}
<li>{{this}}</li>
{{/each}}
</ul>
</div>
<a href="{{dashboardUrl}}" style="display: inline-block; background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px;">
Go to Dashboard
</a>
<p style="margin-top: 30px; color: #6b7280; font-size: 14px;">
Need help? Reply to this email or visit our <a href="{{supportUrl}}">support center</a>.
</p>
</div>
`
},
orderConfirmation: {
subject: "Order #{{orderId}} Confirmed",
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h1>Order Confirmed!</h1>
<p>Hi {{firstName}}, your order has been confirmed.</p>
<table style="width: 100%; border-collapse: collapse;">
<tr style="background: #f3f4f6;">
<th style="padding: 10px; text-align: left;">Item</th>
<th style="padding: 10px; text-align: right;">Qty</th>
<th style="padding: 10px; text-align: right;">Price</th>
</tr>
{{#each items}}
<tr>
<td style="padding: 10px; border-bottom: 1px solid #e5e7eb;">{{name}}</td>
<td style="padding: 10px; border-bottom: 1px solid #e5e7eb; text-align: right;">{{quantity}}</td>
<td style="padding: 10px; border-bottom: 1px solid #e5e7eb; text-align: right;">{{price}}</td>
</tr>
{{/each}}
<tr>
<td colspan="2" style="padding: 10px; text-align: right; font-weight: bold;">Total:</td>
<td style="padding: 10px; text-align: right; font-weight: bold;">{{total}}</td>
</tr>
</table>
</div>
`
}
};
// Simple template engine
function renderTemplate(templateName, data) {
let template = templates[templateName];
if (!template) {
throw new Error(`Template '${templateName}' not found`);
}
let subject = template.subject;
let html = template.html;
// Replace simple variables
Object.entries(data).forEach(([key, value]) => {
if (typeof value === 'string' || typeof value === 'number') {
const regex = new RegExp(`{{${key}}}`, 'g');
subject = subject.replace(regex, value);
html = html.replace(regex, value);
}
});
// Handle arrays with {{#each}}
Object.entries(data).forEach(([key, value]) => {
if (Array.isArray(value)) {
const eachRegex = new RegExp(`{{#each ${key}}}([\\s\\S]*?){{/each}}`, 'g');
html = html.replace(eachRegex, (match, template) => {
return value.map(item => {
if (typeof item === 'object') {
let rendered = template;
Object.entries(item).forEach(([k, v]) => {
rendered = rendered.replace(new RegExp(`{{${k}}}`, 'g'), v);
});
return rendered;
}
return template.replace(/{{this}}/g, item);
}).join('');
});
}
});
return { subject, html };
}
// Render the template
const templateData = $input.first().json;
const rendered = renderTemplate(templateData.templateName, templateData.variables);
return [{
json: {
to: templateData.to,
subject: rendered.subject,
html: rendered.html,
replyTo: templateData.replyTo || 'noreply@example.com'
}
}];Gestionarea atasamentelor
Procesarea atasamentelor din email
Gestioneaza atasamentele primite in mod securizat:
// Function Node: Attachment Processor
const emailData = $input.first().json;
const attachments = emailData.attachments || [];
const processedAttachments = [];
const allowedMimeTypes = [
'application/pdf',
'image/jpeg',
'image/png',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/csv'
];
const maxSizeBytes = 10 * 1024 * 1024; // 10MB
for (const attachment of attachments) {
// Validate mime type
if (!allowedMimeTypes.includes(attachment.mimeType)) {
console.log(`Rejected attachment: ${attachment.filename} - Invalid type: ${attachment.mimeType}`);
continue;
}
// Validate size
const sizeBytes = Buffer.from(attachment.content, 'base64').length;
if (sizeBytes > maxSizeBytes) {
console.log(`Rejected attachment: ${attachment.filename} - Too large: ${sizeBytes} bytes`);
continue;
}
// Sanitize filename
const sanitizedFilename = attachment.filename
.replace(/[^a-zA-Z0-9.-]/g, '_')
.substring(0, 255);
processedAttachments.push({
filename: sanitizedFilename,
mimeType: attachment.mimeType,
content: attachment.content,
sizeBytes: sizeBytes
});
}
return [{
json: {
...emailData,
processedAttachments,
attachmentCount: processedAttachments.length
}
}];Crearea atasamentelor de email
Genereaza si ataseaza fisiere dinamic:
// Function Node: Generate PDF Attachment
const orderData = $input.first().json;
// Generate invoice content (simplified - use a proper PDF library in production)
const invoiceHtml = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { border-bottom: 2px solid #2563eb; padding-bottom: 20px; }
.items { margin-top: 30px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #e5e7eb; }
.total { font-weight: bold; font-size: 18px; }
</style>
</head>
<body>
<div class="header">
<h1>Invoice #${orderData.invoiceNumber}</h1>
<p>Date: ${new Date().toLocaleDateString()}</p>
</div>
<div class="items">
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
${orderData.items.map(item =>
`<tr><td>${item.name}</td><td>${item.qty}</td><td>$${item.price}</td></tr>`
).join('')}
</table>
<p class="total">Total: $${orderData.total}</p>
</div>
</body>
</html>
`;
// In production, convert HTML to PDF using a service
return [{
json: {
...orderData,
attachment: {
filename: `invoice-${orderData.invoiceNumber}.html`,
content: Buffer.from(invoiceHtml).toString('base64'),
contentType: 'text/html'
}
}
}];Orchestrare multi-canal
Email cu fallback pe SMS
Implementeaza livrare fiabila cu fallback-uri:
// Workflow: Multi-Channel Notification
// Node 1: Determine Channel Priority
const notification = $input.first().json;
const userPreferences = notification.userPreferences;
const channels = [];
// Build channel priority based on urgency and preferences
if (notification.urgency === 'critical') {
// Critical: SMS first, then email
if (userPreferences.smsEnabled) channels.push('sms');
channels.push('email');
if (userPreferences.pushEnabled) channels.push('push');
} else if (notification.urgency === 'high') {
// High: Email and push simultaneously
channels.push('email');
if (userPreferences.pushEnabled) channels.push('push');
} else {
// Normal: Email only
channels.push('email');
}
return [{
json: {
...notification,
channelPriority: channels,
currentChannel: 0,
deliveryAttempts: []
}
}];// Node 2: Send via Current Channel (Switch Node routes to appropriate sender)
// After sending, track delivery status
const result = $input.first().json;
const deliveryAttempt = {
channel: result.channel,
timestamp: new Date().toISOString(),
success: result.success,
messageId: result.messageId,
error: result.error || null
};
result.deliveryAttempts.push(deliveryAttempt);
// Check if we need fallback
if (!result.success && result.currentChannel < result.channelPriority.length - 1) {
// Move to next channel
result.currentChannel++;
result.needsRetry = true;
} else {
result.needsRetry = false;
result.finalStatus = result.success ? 'delivered' : 'failed';
}
return [{ json: result }];Gestionarea cozii de emailuri
Coada de email cu rate limiting
Implementeaza o coada de trimitere cu limitarea ratei:
// Function Node: Email Queue Manager
const emailBatch = $input.all();
const rateLimit = 100; // emails per minute
const delayBetweenEmails = 60000 / rateLimit; // ms between emails
const queuedEmails = emailBatch.map((email, index) => ({
json: {
...email.json,
queuePosition: index,
scheduledSendTime: new Date(Date.now() + (index * delayBetweenEmails)).toISOString(),
queueId: `queue_${Date.now()}_${index}`,
status: 'queued'
}
}));
return queuedEmails;Gestionarea bounce-urilor si reclamatiilor
Proceseaza bounce-urile si mentine igiena listei:
// Function Node: Bounce Handler
const webhookData = $input.first().json;
let action = 'none';
let severity = 'info';
switch (webhookData.eventType) {
case 'bounce':
if (webhookData.bounceType === 'Permanent') {
// Hard bounce - remove from list
action = 'unsubscribe';
severity = 'high';
} else {
// Soft bounce - increment counter
action = 'increment_soft_bounce';
severity = 'medium';
}
break;
case 'complaint':
// Spam complaint - immediate removal
action = 'unsubscribe';
severity = 'critical';
break;
case 'unsubscribe':
action = 'unsubscribe';
severity = 'info';
break;
}
return [{
json: {
email: webhookData.email,
eventType: webhookData.eventType,
action,
severity,
timestamp: new Date().toISOString(),
rawEvent: webhookData
}
}];Analiza si tracking email
Tracking deschideri si click-uri
Implementeaza tracking pixel si rescrierea link-urilor:
// Function Node: Add Tracking to Email
const email = $input.first().json;
const trackingDomain = 'https://track.example.com';
const emailId = email.emailId || `email_${Date.now()}`;
// Add tracking pixel for opens
const trackingPixel = `<img src="${trackingDomain}/open/${emailId}" width="1" height="1" style="display:none;" />`;
// Rewrite links for click tracking
let trackedHtml = email.html;
const linkRegex = /<a\s+([^>]*href=["'])([^"']+)(["'][^>]*)>/gi;
trackedHtml = trackedHtml.replace(linkRegex, (match, before, url, after) => {
// Don't track mailto or tel links
if (url.startsWith('mailto:') || url.startsWith('tel:')) {
return match;
}
const encodedUrl = encodeURIComponent(url);
const trackedUrl = `${trackingDomain}/click/${emailId}?url=${encodedUrl}`;
return `<a ${before}${trackedUrl}${after}>`;
});
// Add tracking pixel before closing body tag
trackedHtml = trackedHtml.replace('</body>', `${trackingPixel}</body>`);
return [{
json: {
...email,
emailId,
html: trackedHtml,
tracking: {
openPixelUrl: `${trackingDomain}/open/${emailId}`,
enabled: true
}
}
}];Gestionarea erorilor si logica de retry
Trimitere robusta de email cu retry-uri
// Function Node: Email Sender with Retry Logic
const email = $input.first().json;
const maxRetries = 3;
const retryDelays = [1000, 5000, 15000]; // Exponential backoff
async function sendWithRetry(emailData, attempt = 0) {
try {
// Simulate sending (in production, this calls the email service)
const result = await sendEmail(emailData);
return {
success: true,
messageId: result.messageId,
attempt: attempt + 1
};
} catch (error) {
if (attempt < maxRetries - 1) {
// Determine if error is retryable
const retryableErrors = ['ECONNRESET', 'ETIMEDOUT', 'RATE_LIMIT'];
const isRetryable = retryableErrors.some(e =>
error.message.includes(e) || error.code === e
);
if (isRetryable) {
await new Promise(resolve => setTimeout(resolve, retryDelays[attempt]));
return sendWithRetry(emailData, attempt + 1);
}
}
return {
success: false,
error: error.message,
attempt: attempt + 1
};
}
}
// For n8n, we structure as output
return [{
json: {
...email,
sendConfig: {
maxRetries,
retryDelays,
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'RATE_LIMIT']
}
}
}];Exemplu de workflow pentru productie
Workflow complet de email tranzactional
# Workflow: Transactional Email System
nodes:
- name: Webhook Trigger
type: n8n-nodes-base.webhook
parameters:
path: send-email
method: POST
authentication: headerAuth
- name: Validate Request
type: n8n-nodes-base.function
parameters:
functionCode: |
const data = $input.first().json;
const required = ['to', 'templateName', 'variables'];
const missing = required.filter(f => !data[f]);
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(', ')}`);
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(data.to)) {
throw new Error('Invalid email format');
}
return [{ json: data }];
- name: Render Template
type: n8n-nodes-base.function
# Template rendering code here
- name: Check Suppression List
type: n8n-nodes-base.postgres
parameters:
operation: select
table: email_suppressions
options:
where:
email: '={{ $json.to }}'
- name: Route Based on Suppression
type: n8n-nodes-base.if
parameters:
conditions:
- leftValue: '={{ $json.length }}'
operation: equal
rightValue: 0
- name: Send Email
type: n8n-nodes-base.emailSend
parameters:
fromEmail: '={{ $env.FROM_EMAIL }}'
toEmail: '={{ $json.to }}'
subject: '={{ $json.subject }}'
html: '={{ $json.html }}'
- name: Log Success
type: n8n-nodes-base.postgres
parameters:
operation: insert
table: email_logs
columns: to, template, status, message_id, sent_at
- name: Handle Failure
type: n8n-nodes-base.function
parameters:
functionCode: |
// Log failure and trigger alert if needed
const error = $input.first().json;
return [{
json: {
status: 'failed',
error: error.message,
timestamp: new Date().toISOString()
}
}];Sumar de bune practici
- Valideaza intotdeauna adresele de email inainte de trimitere
- Foloseste template-uri pentru continut de email consistent si usor de intretinut
- Implementeaza tracking pentru deschideri si click-uri
- Gestioneaza bounce-urile pentru a mentine igiena listei
- Limiteaza rata pentru a evita throttling-ul de la provider
- Logheaza totul pentru debugging si conformitate
- Foloseste canale de fallback pentru notificari critice
- Sanitizeaza atasamentele pentru a preveni probleme de securitate
Automatizarea emailului cu n8n ofera flexibilitatea de a construi workflow-uri sofisticate mentinand fiabilitatea necesara pentru sistemele de productie. Cheia este combinarea gestionarii corecte a erorilor cu logarea completa pentru a asigura livrabilitatea si a mentine reputatia expeditorului.
Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →