{
  "name": "FilePost — Send an expiring client preview with SendGrid",
  "nodes": [
    {
      "parameters": {
        "content": "## Expiring preview delivery\n\nThis workflow turns a temporary source URL into a FilePost link that deletes itself, then emails the recipient through SendGrid. There is no long-running Wait node: FilePost enforces expiry at the storage layer.\n\n### Webhook fields\nRequired: `file_url`, `recipient_email`. Optional: `recipient_name`, `project_name`, `message`, `filename`, and `expires_in` (`1h`, `6h`, `24h`, `3d`, or `7d`).\n\n### Setup\n1. Install and connect `n8n-nodes-filepost`.\n2. On **Send Preview Email**, add Header Auth: `Authorization: Bearer YOUR_SENDGRID_API_KEY`.\n3. Set `SENDER_EMAIL` and optionally `SENDER_NAME` in n8n variables.\n\nFilePost URLs are public-by-link. Do not use this template for files that require identity-based access control.",
        "height": 590,
        "width": 640,
        "color": 5
      },
      "id": "bcaee702-fb5c-4985-95d7-75c3ea64fe08",
      "name": "Setup and security note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-840, -420]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "filepost-expiring-client-preview",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "5f07eddb-f024-4d58-9aa2-d8c13669f6c1",
      "name": "Receive Preview Request",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [-800, 100],
      "webhookId": "filepost-expiring-preview-template"
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body || $input.first().json;\nif (!body.file_url) throw new Error('file_url is required');\nconst email = String(body.recipient_email || '').trim().toLowerCase();\nif (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) throw new Error('recipient_email is invalid');\nconst fileUrl = new URL(body.file_url);\nif (!['http:', 'https:'].includes(fileUrl.protocol)) throw new Error('file_url must use http or https');\nconst allowedExpiry = new Set(['1h', '6h', '24h', '3d', '7d']);\nconst expiresIn = allowedExpiry.has(String(body.expires_in || '')) ? String(body.expires_in) : '24h';\nconst clean = (value, fallback = '', max = 500) => String(value || fallback).trim().replace(/[\\r\\n]+/g, ' ').slice(0, max);\nconst filename = clean(body.filename || fileUrl.pathname.split('/').pop(), 'preview.bin', 180);\nreturn [{ json: {\n  fileUrl: fileUrl.toString(),\n  recipientEmail: email,\n  recipientName: clean(body.recipient_name, 'there', 120),\n  projectName: clean(body.project_name, 'Your preview', 180),\n  message: clean(body.message, '', 1000),\n  filename,\n  expiresIn,\n  requestedAt: new Date().toISOString()\n} }];"
      },
      "id": "74010588-f0fd-4ae4-ae60-92b0d56bdf7e",
      "name": "Validate Preview Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [-560, 100]
    },
    {
      "parameters": {
        "url": "={{ $json.fileUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "id": "9227801d-4dd0-4a30-be42-452932de47bb",
      "name": "Download Preview File",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [-320, 100]
    },
    {
      "parameters": {
        "operation": "upload",
        "binaryPropertyName": "data",
        "expiresIn": "={{ $('Validate Preview Request').first().json.expiresIn }}",
        "filenameMode": "public"
      },
      "id": "398406fb-e068-443f-8040-1a7c2cefa22c",
      "name": "Create Expiring FilePost Link",
      "type": "n8n-nodes-filepost.filePost",
      "typeVersion": 1,
      "position": [-80, 100]
    },
    {
      "parameters": {
        "jsCode": "const meta = $('Validate Preview Request').first().json;\nconst hosted = $input.first().json;\nconst escapeHtml = (value) => String(value).replace(/[&<>\"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[char]);\nconst expiryLabels = { '1h': 'one hour', '6h': 'six hours', '24h': '24 hours', '3d': 'three days', '7d': 'seven days' };\nconst senderEmail = String($vars.SENDER_EMAIL || '').trim();\nif (!senderEmail) throw new Error('Set the SENDER_EMAIL n8n variable before running this workflow');\nconst senderName = String($vars.SENDER_NAME || 'File delivery').trim();\nconst note = meta.message ? `<p style=\"padding:12px 16px;background:#f3f4f6;border-radius:8px;\">${escapeHtml(meta.message)}</p>` : '';\nconst html = `<div style=\"font-family:Arial,sans-serif;max-width:560px;margin:auto;color:#111827;line-height:1.6\"><h1 style=\"font-size:24px\">${escapeHtml(meta.projectName)}</h1><p>Hi ${escapeHtml(meta.recipientName)},</p><p>Your preview <strong>${escapeHtml(meta.filename)}</strong> is ready.</p>${note}<p style=\"margin:28px 0\"><a href=\"${hosted.url}\" style=\"background:#111827;color:#fff;text-decoration:none;padding:12px 18px;border-radius:7px\">Open preview</a></p><p style=\"font-size:13px;color:#6b7280\">This public-by-link URL expires in ${expiryLabels[meta.expiresIn]}. Do not forward it.</p></div>`;\nconst text = `Hi ${meta.recipientName},\\n\\nYour preview ${meta.filename} is ready: ${hosted.url}\\n\\nThis public-by-link URL expires in ${expiryLabels[meta.expiresIn]}. Do not forward it.`;\nreturn [{ json: {\n  sendgridBody: { personalizations: [{ to: [{ email: meta.recipientEmail }] }], from: { email: senderEmail, name: senderName }, subject: `${meta.projectName} — preview ready`, content: [{ type: 'text/plain', value: text }, { type: 'text/html', value: html }] },\n  recipientEmail: meta.recipientEmail,\n  projectName: meta.projectName,\n  filename: meta.filename,\n  expiresIn: meta.expiresIn,\n  previewUrl: hosted.url,\n  filepostFileId: hosted.file_id,\n  expiresAt: hosted.expires_at\n} }];"
      },
      "id": "c03cd8ac-3897-46b0-9650-ccf3be141ea6",
      "name": "Build Preview Email",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [160, 100]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.sendgrid.com/v3/mail/send",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.sendgridBody) }}",
        "options": {}
      },
      "id": "fd13ae09-6bd5-4c46-96c5-56d604a33422",
      "name": "Send Preview Email",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [400, 100]
    },
    {
      "parameters": {
        "jsCode": "const delivery = $('Build Preview Email').first().json;\nreturn [{ json: {\n  success: true,\n  recipient_email: delivery.recipientEmail,\n  project_name: delivery.projectName,\n  filename: delivery.filename,\n  preview_url: delivery.previewUrl,\n  expires_in: delivery.expiresIn,\n  expires_at: delivery.expiresAt,\n  filepost_file_id: delivery.filepostFileId\n} }];"
      },
      "id": "681c1706-d752-42c5-a939-1bc97c715c65",
      "name": "Build Delivery Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [640, 100]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {"responseCode": 201}
      },
      "id": "fa4ef8db-ad94-4fe6-9450-5dc5c1ac0739",
      "name": "Return Delivery Result",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [880, 100]
    }
  ],
  "connections": {
    "Receive Preview Request": {"main": [[{"node": "Validate Preview Request", "type": "main", "index": 0}]]},
    "Validate Preview Request": {"main": [[{"node": "Download Preview File", "type": "main", "index": 0}]]},
    "Download Preview File": {"main": [[{"node": "Create Expiring FilePost Link", "type": "main", "index": 0}]]},
    "Create Expiring FilePost Link": {"main": [[{"node": "Build Preview Email", "type": "main", "index": 0}]]},
    "Build Preview Email": {"main": [[{"node": "Send Preview Email", "type": "main", "index": 0}]]},
    "Send Preview Email": {"main": [[{"node": "Build Delivery Result", "type": "main", "index": 0}]]},
    "Build Delivery Result": {"main": [[{"node": "Return Delivery Result", "type": "main", "index": 0}]]}
  },
  "active": false,
  "settings": {"executionOrder": "v1"},
  "versionId": "f383d126-cbf4-4f97-9a83-f98aa564e765",
  "meta": {"templateCredsSetupCompleted": false},
  "tags": []
}
