AutomationFlowsEmail & Gmail › Slip Gaji · Pending Only → PDF → Gmail (best Practice)

Slip Gaji · Pending Only → PDF → Gmail (best Practice)

Slip Gaji · Pending only → PDF → Gmail (best practice). Uses googleSheets, gmail. Event-driven trigger; 13 nodes.

Event trigger★★★★☆ complexity13 nodesGoogle SheetsGmail
Email & Gmail Trigger: Event Nodes: 13 Complexity: ★★★★☆ Added:

This workflow follows the Gmail → Google Sheets recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Slip Gaji \u00b7 Pending only \u2192 PDF \u2192 Gmail (best practice)",
  "nodes": [
    {
      "parameters": {
        "content": "## Best practice slip gaji\n\n**Jangan** tarik semua lalu Limit (buang sisa).\n**Ya:** filter `periode` + `slip_status=pending` dulu.\n\nAlur:\n1. Config (periode, dry_run_email, max_per_run)\n2. Baca sheet\n3. Filter pending + periode\n4. Safety Limit (max_per_run)\n5. Batch 1 \u2192 PDF \u2192 Gmail\n6. Sukses \u2192 Update sent | Gagal \u2192 skip update\n7. Jeda \u2192 loop \u00b7 resume-safe\n\nCSV: `data/samples/karyawan-slip-gaji-1000.csv`\n",
        "height": 420,
        "width": 380,
        "color": 4
      },
      "id": "sticky-guide",
      "name": "Panduan best practice",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -520,
        160
      ]
    },
    {
      "parameters": {},
      "id": "manual",
      "name": "Mulai",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        320
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-periode",
              "name": "periode",
              "value": "2026-08",
              "type": "string"
            },
            {
              "id": "cfg-dry",
              "name": "dry_run_email",
              "value": "egagofurtriwahana@gmail.com",
              "type": "string"
            },
            {
              "id": "cfg-max",
              "name": "max_per_run",
              "value": 5,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "config",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        320
      ],
      "notes": "periode = bulan gajian. dry_run_email = isi emailmu untuk tes (semua slip ke situ). Kosongkan dry_run_email untuk kirim ke email karyawan. max_per_run = sabuk pengaman per eksekusi."
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "PASTE_SHEET_DOCUMENT_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "karyawan",
          "mode": "name"
        },
        "options": {}
      },
      "id": "sheets-read",
      "name": "Baca Data Karyawan",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        460,
        320
      ],
      "notes": "Idealnya filter di sumber (DB). Sheets: baca lalu Filter node. Untuk data sangat besar, pindah ke DB + WHERE pending."
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f-periode",
              "leftValue": "={{ $json.periode }}",
              "rightValue": "={{ $('Config').item.json.periode }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "f-status",
              "leftValue": "={{ $json.slip_status }}",
              "rightValue": "pending",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "filter-pending",
      "name": "Filter pending + periode",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        700,
        320
      ],
      "notes": "Hanya proses slip_status=pending dan periode = Config. Resume: yang sudah sent tidak diambil lagi."
    },
    {
      "parameters": {
        "maxItems": "={{ $('Config').item.json.max_per_run }}"
      },
      "id": "safety-limit",
      "name": "Safety max per run",
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        940,
        320
      ],
      "notes": "Sabuk pengaman: max N per eksekusi (dari Config). Bukan pengganti filter pending. Run ulang = lanjut sisa pending."
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "split-batch",
      "name": "Satu per satu",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        1180,
        320
      ],
      "notes": "1 karyawan per loop. Loop kembali setelah update status."
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Mode: Run Once for Each Item\n// Slip gaji PDF \u2014 DOT style (hijau + putih, minimal, elegan)\n\nconst e = $json;\n\nfunction num(n) {\n  return Number(n) || 0;\n}\nfunction idr(n) {\n  return 'Rp ' + num(n).toLocaleString('id-ID');\n}\nfunction escPdf(s) {\n  return String(s ?? '')\n    .replace(/\\\\/g, '\\\\\\\\')\n    .replace(/\\(/g, '\\\\(')\n    .replace(/\\)/g, '\\\\)');\n}\n\nconst gajiPokok = num(e.gaji_pokok);\nconst tTrans = num(e.tunjangan_transport);\nconst tMakan = num(e.tunjangan_makan);\nconst tJab = num(e.tunjangan_jabatan);\nconst lembur = num(e.lembur);\nconst bruto = num(e.bruto) || (gajiPokok + tTrans + tMakan + tJab + lembur);\nconst bpjsKes = num(e.potongan_bpjs_kesehatan);\nconst bpjsTk = num(e.potongan_bpjs_tk);\nconst pph = num(e.potongan_pph21);\nconst potLain = num(e.potongan_lain);\nconst totalPot = num(e.total_potongan) || (bpjsKes + bpjsTk + pph + potLain);\nconst neto = num(e.gaji_neto) || (bruto - totalPot);\nconst periode = e.periode || '2026-08';\nconst empId = e.employee_id || '-';\nconst nama = e.nama || '-';\nconst emailAsli = e.email || '';\nconst jabatan = e.jabatan || '-';\nconst dept = e.departemen || '-';\nconst bank = e.bank || '-';\nconst rek = e.no_rekening || '-';\n\nconst cfg = $('Config').first().json;\nconst emailTujuan = (cfg.dry_run_email && String(cfg.dry_run_email).trim())\n  ? String(cfg.dry_run_email).trim()\n  : emailAsli;\n\n// ---------- PDF DOT (A4, hijau + putih saja) ----------\nconst W = 595;\nconst H = 842;\nconst ML = 48;\nconst MR = 48;\nconst contentW = W - ML - MR;\n\nconst G = {\n  900: [0.07, 0.18, 0.11],\n  700: [0.1, 0.35, 0.2],\n  500: [0.18, 0.48, 0.3],\n  200: [0.86, 0.93, 0.88],\n  100: [0.94, 0.97, 0.95],\n  50: [0.98, 0.99, 0.98],\n};\nconst WHITE = [1, 1, 1];\n\nfunction rgb(a) {\n  return a.map(function (x) {\n    return Number(x.toFixed(3));\n  }).join(' ');\n}\n\nconst ops = [];\nfunction push() {\n  for (let i = 0; i < arguments.length; i++) ops.push(arguments[i]);\n}\nfunction fillRect(x, y, w, h, c) {\n  push(rgb(c) + ' rg', x + ' ' + y + ' ' + w + ' ' + h + ' re', 'f');\n}\nfunction hLine(x1, y, x2, c, lw) {\n  push(rgb(c) + ' RG', (lw || 0.5) + ' w', x1 + ' ' + y + ' m', x2 + ' ' + y + ' l', 'S');\n}\nfunction dot(cx, cy, r, c) {\n  const k = 0.5522847498 * r;\n  push(\n    rgb(c) + ' rg',\n    (cx + r) + ' ' + cy + ' m',\n    (cx + r) + ' ' + (cy + k) + ' ' + (cx + k) + ' ' + (cy + r) + ' ' + cx + ' ' + (cy + r) + ' c',\n    (cx - k) + ' ' + (cy + r) + ' ' + (cx - r) + ' ' + (cy + k) + ' ' + (cx - r) + ' ' + cy + ' c',\n    (cx - r) + ' ' + (cy - k) + ' ' + (cx - k) + ' ' + (cy - r) + ' ' + cx + ' ' + (cy - r) + ' c',\n    (cx + k) + ' ' + (cy - r) + ' ' + (cx + r) + ' ' + (cy - k) + ' ' + (cx + r) + ' ' + cy + ' c',\n    'f'\n  );\n}\nfunction textAt(x, y, size, str, font, color) {\n  push(\n    'BT',\n    '/' + (font || 'F1') + ' ' + size + ' Tf',\n    rgb(color || G[900]) + ' rg',\n    '1 0 0 1 ' + x + ' ' + y + ' Tm',\n    '(' + escPdf(str) + ') Tj',\n    'ET'\n  );\n}\nfunction textRight(xRight, y, size, str, font, color) {\n  const w = String(str).length * size * 0.5;\n  textAt(xRight - w, y, size, str, font, color);\n}\n\n// page\nfillRect(0, 0, W, H, WHITE);\n// top bar\nfillRect(0, H - 6, W, 6, G[700]);\n\nlet y = H - 42;\ndot(ML + 3, y + 4, 2.2, G[700]);\ndot(ML + 11, y + 4, 2.2, G[700]);\ndot(ML + 19, y + 4, 2.2, G[500]);\ntextAt(ML + 30, y, 9, 'PT CONTOH TEKNOLOGI NUSANTARA', 'F2', G[700]);\ntextRight(W - MR, y, 8, 'SLIP GAJI', 'F1', G[500]);\n\ny = H - 68;\ntextAt(ML, y, 20, 'Slip Gaji', 'F2', G[900]);\ntextAt(ML, y - 16, 9, 'Periode  ' + periode, 'F1', G[500]);\ntextRight(W - MR, y, 9, empId, 'F2', G[900]);\ntextRight(W - MR, y - 14, 8, 'ID Karyawan', 'F1', G[500]);\n\ny = H - 100;\nhLine(ML, y, W - MR, G[200], 0.7);\n\n// identity grid\ny -= 28;\nconst idTop = y;\nconst colL = ML;\nconst colR = ML + contentW * 0.52;\n\nfunction field(x, yy, label, value) {\n  textAt(x, yy, 7.5, String(label).toUpperCase(), 'F1', G[500]);\n  textAt(x, yy - 13, 10, value, 'F1', G[900]);\n}\n\nfield(colL, idTop, 'Nama', nama);\nfield(colR, idTop, 'Jabatan', jabatan);\nfield(colL, idTop - 36, 'Departemen', dept);\nfield(colR, idTop - 36, 'Email', emailAsli || '-');\nfield(colL, idTop - 72, 'Rekening', bank + '  ' + rek);\nfield(colR, idTop - 72, 'Periode', periode);\n\ny = idTop - 100;\nhLine(ML, y, W - MR, G[200], 0.7);\n\nfunction sectionTitle(yy, title) {\n  dot(ML + 2.5, yy + 3, 2.4, G[700]);\n  textAt(ML + 14, yy, 9, title.toUpperCase(), 'F2', G[700]);\n  return yy - 8;\n}\n\nfunction tableBlock(startY, rows, totalLabel, totalValue) {\n  const rowH = 18;\n  const labelX = ML + 4;\n  const amtR = W - MR - 4;\n  let cy = startY;\n  hLine(ML, cy, W - MR, G[200], 0.5);\n  cy -= 4;\n\n  rows.forEach(function (r, i) {\n    cy -= rowH;\n    if (i % 2 === 0) fillRect(ML, cy - 2, contentW, rowH, G[50]);\n    textAt(labelX, cy + 4, 9, r.label, 'F1', G[900]);\n    textRight(amtR, cy + 4, 9, r.value, 'F1', G[900]);\n  });\n\n  cy -= 6;\n  hLine(ML, cy + rowH - 2, W - MR, G[200], 0.6);\n  cy -= rowH;\n  textAt(labelX, cy + 4, 9, totalLabel, 'F2', G[700]);\n  textRight(amtR, cy + 4, 9, totalValue, 'F2', G[700]);\n  hLine(ML, cy - 6, W - MR, G[700], 0.9);\n  return cy - 14;\n}\n\ny = sectionTitle(y - 22, 'Pendapatan');\ny = tableBlock(\n  y,\n  [\n    { label: 'Gaji pokok', value: idr(gajiPokok) },\n    { label: 'Tunjangan transport', value: idr(tTrans) },\n    { label: 'Tunjangan makan', value: idr(tMakan) },\n    { label: 'Tunjangan jabatan', value: idr(tJab) },\n    { label: 'Lembur', value: idr(lembur) },\n  ],\n  'Total bruto',\n  idr(bruto)\n);\n\ny = sectionTitle(y - 10, 'Potongan');\ny = tableBlock(\n  y,\n  [\n    { label: 'BPJS Kesehatan', value: idr(bpjsKes) },\n    { label: 'BPJS Ketenagakerjaan', value: idr(bpjsTk) },\n    { label: 'PPh 21', value: idr(pph) },\n    { label: 'Potongan lain', value: idr(potLain) },\n  ],\n  'Total potongan',\n  idr(totalPot)\n);\n\n// neto bar\ny -= 18;\nhLine(ML, y + 20, W - MR, G[200], 0.5);\nfillRect(ML, y - 8, contentW, 36, G[700]);\ntextAt(ML + 14, y + 10, 8, 'GAJI BERSIH (NETO)', 'F1', WHITE);\ntextAt(ML + 14, y - 2, 14, idr(neto), 'F2', WHITE);\ntextRight(W - MR - 14, y + 4, 8, periode, 'F1', G[200]);\n\n// footer\nconst footY = 40;\nhLine(ML, footY + 22, W - MR, G[200], 0.5);\ndot(ML + 2, footY + 8, 1.4, G[500]);\ndot(ML + 8, footY + 8, 1.4, G[500]);\ndot(ML + 14, footY + 8, 1.4, G[700]);\ntextAt(\n  ML + 24,\n  footY + 5,\n  7,\n  'Dokumen rahasia  -  digenerate otomatis  -  hubungi HR bila ada ketidaksesuaian',\n  'F1',\n  G[500]\n);\ntextAt(ML + 24, footY - 8, 7, 'PT Contoh Teknologi Nusantara', 'F1', G[500]);\n\nconst contentStream = ops.join('\\n');\n\nconst parts = [];\nparts.push('1 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj\\n');\nparts.push('2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj\\n');\nparts.push(\n  '3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources<< /Font<< /F1 5 0 R /F2 6 0 R >> >> >>endobj\\n'\n);\nparts.push(\n  '4 0 obj<< /Length ' +\n    Buffer.byteLength(contentStream, 'utf8') +\n    ' >>stream\\n' +\n    contentStream +\n    '\\nendstream\\nendobj\\n'\n);\nparts.push('5 0 obj<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>endobj\\n');\nparts.push('6 0 obj<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>endobj\\n');\n\nlet pdf = '%PDF-1.4\\n';\nconst offsets = [0];\nfor (let i = 0; i < parts.length; i++) {\n  offsets.push(Buffer.byteLength(pdf, 'utf8'));\n  pdf += parts[i];\n}\nconst xrefPos = Buffer.byteLength(pdf, 'utf8');\nlet xref = 'xref\\n0 ' + (parts.length + 1) + '\\n';\nxref += '0000000000 65535 f \\n';\nfor (let i = 1; i <= parts.length; i++) {\n  xref += String(offsets[i]).padStart(10, '0') + ' 00000 n \\n';\n}\npdf += xref;\npdf += 'trailer<< /Size ' + (parts.length + 1) + ' /Root 1 0 R >>\\n';\npdf += 'startxref\\n' + xrefPos + '\\n%%EOF';\n\nconst fileName = 'slip-gaji-' + empId + '-' + periode + '.pdf';\n\n// Email HTML \u2014 DOT, hijau + putih\nconst emailHtml =\n  '<!DOCTYPE html><html><body style=\"margin:0;padding:0;background:#ffffff;font-family:Helvetica,Arial,sans-serif;color:#122E1C;\">' +\n  '<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff;padding:32px 16px;\"><tr><td align=\"center\">' +\n  '<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:520px;border-top:4px solid #1A5933;\">' +\n  '<tr><td style=\"padding:28px 0 8px;\">' +\n  '<p style=\"margin:0 0 4px;font-size:11px;letter-spacing:0.08em;color:#2E7A4C;\">PT CONTOH TEKNOLOGI NUSANTARA</p>' +\n  '<h1 style=\"margin:0;font-size:22px;font-weight:700;color:#122E1C;\">Slip Gaji</h1>' +\n  '<p style=\"margin:8px 0 0;font-size:13px;color:#2E7A4C;\">Periode ' +\n  periode +\n  ' &nbsp;\u00b7&nbsp; ' +\n  empId +\n  '</p>' +\n  '</td></tr>' +\n  '<tr><td style=\"padding:16px 0;border-top:1px solid #DBEDE1;border-bottom:1px solid #DBEDE1;\">' +\n  '<p style=\"margin:0 0 12px;font-size:14px;line-height:1.5;\">Halo <strong>' +\n  nama +\n  '</strong>,</p>' +\n  '<p style=\"margin:0;font-size:14px;line-height:1.5;color:#122E1C;\">Slip gaji periode <strong>' +\n  periode +\n  '</strong> terlampir dalam format PDF.</p>' +\n  '</td></tr>' +\n  '<tr><td style=\"padding:20px 0 8px;\">' +\n  '<table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"font-size:13px;\">' +\n  '<tr><td style=\"padding:6px 0;color:#2E7A4C;\">Gaji bruto</td><td style=\"padding:6px 0;text-align:right;\">' +\n  idr(bruto) +\n  '</td></tr>' +\n  '<tr><td style=\"padding:6px 0;color:#2E7A4C;\">Total potongan</td><td style=\"padding:6px 0;text-align:right;\">' +\n  idr(totalPot) +\n  '</td></tr>' +\n  '<tr><td style=\"padding:12px 0 0;border-top:1px solid #DBEDE1;font-weight:700;\">Gaji bersih</td>' +\n  '<td style=\"padding:12px 0 0;border-top:1px solid #DBEDE1;text-align:right;font-weight:700;color:#1A5933;\">' +\n  idr(neto) +\n  '</td></tr>' +\n  '</table></td></tr>' +\n  '<tr><td style=\"padding:24px 0 0;font-size:11px;color:#2E7A4C;line-height:1.5;\">' +\n  'Dokumen rahasia. Hubungi HR bila ada ketidaksesuaian.<br/>Email otomatis via n8n.' +\n  '</td></tr>' +\n  '</table></td></tr></table></body></html>';\n\nreturn {\n  json: {\n    employee_id: empId,\n    nama: nama,\n    email: emailTujuan,\n    email_asli: emailAsli,\n    jabatan: jabatan,\n    departemen: dept,\n    periode: periode,\n    gaji_bruto: bruto,\n    gaji_neto: neto,\n    email_subject: 'Slip Gaji ' + periode + ' \u2014 ' + nama + ' (' + empId + ')',\n    email_html: emailHtml,\n    fileName: fileName,\n    dry_run: !!(cfg.dry_run_email && String(cfg.dry_run_email).trim()),\n  },\n  binary: {\n    slipPdf: {\n      data: Buffer.from(pdf, 'utf8').toString('base64'),\n      mimeType: 'application/pdf',\n      fileName: fileName,\n      fileExtension: 'pdf',\n    },\n  },\n};\n"
      },
      "id": "code-pdf",
      "name": "Generate Slip PDF",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        200
      ],
      "notes": "PDF DOT style: hijau+putih, minimal, elegan. Run Once for Each Item."
    },
    {
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "={{ $json.email_subject }}",
        "emailType": "html",
        "message": "={{ $json.email_html }}",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {
                "property": "slipPdf"
              }
            ]
          }
        }
      },
      "id": "gmail-send",
      "name": "Kirim Gmail + PDF",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1700,
        200
      ],
      "notes": "Gmail OAuth2 + slipPdf. onError=continueErrorOutput: success\u2192Update sent, error\u2192Jeda (tetap pending). Dry-run: Config.dry_run_email.",
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "PASTE_SHEET_DOCUMENT_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "karyawan",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "employee_id": "={{ $('Generate Slip PDF').item.json.employee_id }}",
            "slip_status": "sent",
            "slip_sent_at": "={{ $now.setZone('Asia/Jakarta').toISO() }}"
          },
          "matchingColumns": [
            "employee_id"
          ],
          "schema": [
            {
              "id": "employee_id",
              "displayName": "employee_id",
              "required": false,
              "defaultMatch": true,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "slip_status",
              "displayName": "slip_status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            },
            {
              "id": "slip_sent_at",
              "displayName": "slip_sent_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "sheets-update-sent",
      "name": "Update status sent",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1960,
        200
      ],
      "notes": "employee_id dari Generate Slip PDF (bukan $json Gmail \u2014 response Gmail menimpa field). Match employee_id \u2192 slip_status=sent. Resume-safe."
    },
    {
      "parameters": {
        "amount": 2,
        "unit": "seconds"
      },
      "id": "wait-rate",
      "name": "Jeda 2s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2220,
        200
      ],
      "notes": "Rate limit Gmail. Loop balik ke batch."
    },
    {
      "parameters": {
        "amount": 2,
        "unit": "seconds"
      },
      "id": "wait-on-error",
      "name": "Jeda error 2s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1960,
        420
      ],
      "notes": "Gmail gagal \u2192 JANGAN update sent (tetap pending). Jeda lalu lanjut batch berikutnya."
    },
    {
      "parameters": {
        "content": "## Error path\n\nGmail gagal \u2192 **skip update sent** \u2192 jeda \u2192 loop batch.\nBaris tetap `pending` \u2192 run berikutnya coba lagi.\n\nJangan tandai `sent` sebelum email sukses.",
        "height": 200,
        "width": 300,
        "color": 3
      },
      "id": "sticky-error",
      "name": "Panduan error",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1700,
        400
      ]
    }
  ],
  "connections": {
    "Mulai": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Baca Data Karyawan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Baca Data Karyawan": {
      "main": [
        [
          {
            "node": "Filter pending + periode",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter pending + periode": {
      "main": [
        [
          {
            "node": "Safety max per run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Safety max per run": {
      "main": [
        [
          {
            "node": "Satu per satu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Satu per satu": {
      "main": [
        [
          {
            "node": "Generate Slip PDF",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Generate Slip PDF": {
      "main": [
        [
          {
            "node": "Kirim Gmail + PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Kirim Gmail + PDF": {
      "main": [
        [
          {
            "node": "Update status sent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Jeda error 2s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update status sent": {
      "main": [
        [
          {
            "node": "Jeda 2s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jeda 2s": {
      "main": [
        [
          {
            "node": "Satu per satu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jeda error 2s": {
      "main": [
        [
          {
            "node": "Satu per satu",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "payroll"
    },
    {
      "name": "best-practice"
    },
    {
      "name": "gmail"
    }
  ]
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Slip Gaji · Pending only → PDF → Gmail (best practice). Uses googleSheets, gmail. Event-driven trigger; 13 nodes.

Source: https://github.com/egagofur/n8n-boilerplate/blob/main/case-slip-gaji/workflow-slip-gaji.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

This workflow ingests proof-of-delivery and completion documents from Gmail or a webhook, extracts key fields with an OpenRouter vision model, reconciles them against Google Sheets dispatch data, arch

Gmail Trigger, HTTP Request, Google Sheets +2
Email & Gmail

Loan eligibility workflow. Uses formTrigger, googleSheets, gmail. Event-driven trigger; 53 nodes.

Form Trigger, Google Sheets, Gmail
Email & Gmail

Splitout Code. Uses manualTrigger, httpRequest, stickyNote, splitOut. Event-driven trigger; 46 nodes.

HTTP Request, Execute Workflow Trigger, Gmail +1
Email & Gmail

Automate CSV imports into HubSpot without the mess. Powered by n8n. Supercharged by Pollup AI.

HTTP Request, Execute Workflow Trigger, Gmail +1
Email & Gmail

Automate event registration with capacity management, a waitlist, and multi-tier PDF ticket generation using PDF Generator API. When attendees register, the workflow checks available spots, routes by

Form Trigger, Google Sheets, @Pdfgeneratorapi/N8N Nodes Pdf Generator Api +2