n8n Automation

Automatizare email cu n8n: ghid complet de implementare a workflow-urilor

Petru Constantin
--10 min lectura
#n8n#email automation#workflow automation#SMTP#IMAP

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

  1. Valideaza intotdeauna adresele de email inainte de trimitere
  2. Foloseste template-uri pentru continut de email consistent si usor de intretinut
  3. Implementeaza tracking pentru deschideri si click-uri
  4. Gestioneaza bounce-urile pentru a mentine igiena listei
  5. Limiteaza rata pentru a evita throttling-ul de la provider
  6. Logheaza totul pentru debugging si conformitate
  7. Foloseste canale de fallback pentru notificari critice
  8. 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 →

Ai nevoie de ajutor cu conformitatea EU AI Act sau securitatea AI?

Programeaza o consultatie gratuita de 30 de minute. Fara obligatii.

Programeaza un Apel

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.