{
  "name": "Datto RMM — Prospect Agent Deployment",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "datto-deploy",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-trigger",
      "name": "Webhook — BDR Portal Submit",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [240, 300],
      "notes": "Receives the JSON payload from the BDR intake portal. No authentication needed — add IP allowlisting in n8n settings if deploying internally."
    },
    {
      "parameters": {
        "jsCode": "// Validate required fields and normalize payload\nconst body = $input.first().json.body;\n\nconst required = ['company_name','contact_name','contact_email','site_type','assessment_scope','bdr_name','bdr_email'];\nfor (const f of required) {\n  if (!body[f] || (Array.isArray(body[f]) && body[f].length === 0)) {\n    throw new Error(`Missing required field: ${f}`);\n  }\n}\n\n// Slugify company name for Datto site name (no special chars)\nconst slug = body.company_name\n  .toLowerCase()\n  .replace(/[^a-z0-9]+/g, '-')\n  .replace(/(^-|-$)/g, '');\n\nconst siteTag = `PROSPECT-${slug.toUpperCase()}`;\n\nreturn [{\n  json: {\n    ...body,\n    site_name: `[PROSPECT] ${body.company_name}`,\n    site_description: `Prospect trial site — created by ${body.bdr_name} on ${new Date().toISOString().split('T')[0]}`,\n    site_tag: siteTag,\n    scope_label: body.assessment_scope.map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(', '),\n    expiry_days: body.agent_expiry_days || 7\n  }\n}];\n"
      },
      "id": "validate-normalize",
      "name": "Validate & Normalize Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [460, 300],
      "notes": "Validates required fields, slugifies company name, and builds the Datto site name/tag."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://{{$env.DATTO_RMM_DOMAIN}}/api/v2/site",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Content-Type", "value": "application/json" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": []
        },
        "specifyBody": "json",
        "jsonBody": "={\n  \"name\": \"{{ $json.site_name }}\",\n  \"description\": \"{{ $json.site_description }}\",\n  \"siteType\": \"{{ $json.site_type }}\",\n  \"notes\": \"Prospect | BDR: {{ $json.bdr_name }} ({{ $json.bdr_email }}) | Scope: {{ $json.scope_label }} | Auto-created {{ $now.toISODate() }}\",\n  \"autotaskCompanyId\": null\n}",
        "options": {}
      },
      "id": "datto-create-site",
      "name": "Datto RMM — Create Site",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [680, 300],
      "credentials": {
        "httpBasicAuth": {
          "id": "datto-rmm-api",
          "name": "Datto RMM API"
        }
      },
      "notes": "Creates the prospect site in Datto RMM. Requires DATTO_RMM_DOMAIN env var (e.g. yourcompany.centrastage.net) and httpBasicAuth credential with API key as username and secret as password."
    },
    {
      "parameters": {
        "jsCode": "// Extract the new site UID from Datto response\nconst site = $input.first().json;\nconst prev = $('Validate & Normalize Payload').first().json;\n\nif (!site.uid) {\n  throw new Error('Datto site creation failed — no UID returned. Response: ' + JSON.stringify(site));\n}\n\nreturn [{\n  json: {\n    ...prev,\n    datto_site_uid: site.uid,\n    datto_site_id: site.id\n  }\n}];\n"
      },
      "id": "extract-site-uid",
      "name": "Extract Site UID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [900, 300]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://{{$env.DATTO_RMM_DOMAIN}}/api/v2/site/{{$json.datto_site_uid}}/agents",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "options": {}
      },
      "id": "datto-get-agent-url",
      "name": "Datto RMM — Get Agent Installer URL",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1120, 300],
      "credentials": {
        "httpBasicAuth": {
          "id": "datto-rmm-api",
          "name": "Datto RMM API"
        }
      },
      "notes": "Fetches the installer download URLs for the newly created site. Returns Windows .exe, macOS .pkg, and Linux .sh URLs."
    },
    {
      "parameters": {
        "jsCode": "// Extract installer links from agent response\nconst agents = $input.first().json;\nconst prev = $('Extract Site UID').first().json;\n\n// Datto returns installer links under agentBrowserUrl or similar\n// Adjust key names based on your Datto API version response\nconst installers = agents.agentLinks || agents.links || agents;\n\nconst windowsUrl = (installers.windows || installers.windowsInstaller || '#WINDOWS_INSTALLER_URL');\nconst macUrl = (installers.mac || installers.macInstaller || '#MAC_INSTALLER_URL');\nconst linuxUrl = (installers.linux || installers.linuxInstaller || '#LINUX_INSTALLER_URL');\n\n// Expiry label for email\nconst expiryDate = new Date();\nexpiryDate.setDate(expiryDate.getDate() + (prev.expiry_days || 7));\nconst expiryLabel = expiryDate.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });\n\nreturn [{\n  json: {\n    ...prev,\n    installer_windows: windowsUrl,\n    installer_mac: macUrl,\n    installer_linux: linuxUrl,\n    installer_expiry_label: expiryLabel\n  }\n}];\n"
      },
      "id": "extract-installer-links",
      "name": "Extract Installer Links",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1340, 300],
      "notes": "Pulls Windows/Mac/Linux installer URLs from the Datto response. Adjust property names to match your Datto API version."
    },
    {
      "parameters": {
        "fromEmail": "={{ $env.FROM_EMAIL }}",
        "toEmail": "={{ $json.contact_email }}",
        "subject": "={{ $json.company_name }} — Your IT Assessment Installer is Ready",
        "emailType": "html",
        "message": "=<!DOCTYPE html>\n<html>\n<head>\n  <meta charset='UTF-8'>\n  <meta name='viewport' content='width=device-width,initial-scale=1'>\n</head>\n<body style='margin:0;padding:0;background:#f5f5f3;font-family:\"Helvetica Neue\",Helvetica,Arial,sans-serif;'>\n\n<table width='100%' cellpadding='0' cellspacing='0' style='background:#f5f5f3;padding:40px 0;'>\n<tr><td align='center'>\n\n<table width='600' cellpadding='0' cellspacing='0' style='background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e3df;'>\n\n  <!-- Header -->\n  <tr>\n    <td style='background:#0f1117;padding:24px 40px;'>\n      <p style='margin:0;font-family:\"Courier New\",monospace;font-size:13px;letter-spacing:0.08em;text-transform:uppercase;color:#ffffff;'>YourMSP</p>\n    </td>\n  </tr>\n\n  <!-- Body -->\n  <tr>\n    <td style='padding:40px 40px 32px;'>\n\n      <h1 style='margin:0 0 16px;font-size:26px;font-weight:600;color:#0f1117;line-height:1.25;'>Hi {{ $json.contact_name }},</h1>\n\n      <p style='margin:0 0 20px;font-size:15px;line-height:1.7;color:#444;'>{{ $json.bdr_name }} from YourMSP has initiated a complimentary IT assessment for <strong>{{ $json.company_name }}</strong>. To get started, you&rsquo;ll need to install a lightweight, read-only monitoring agent on your endpoints.</p>\n\n      {{ $json.notes ? '<p style=\"margin:0 0 20px;padding:14px 16px;background:#eef2ff;border-left:3px solid #1a3de4;border-radius:4px;font-size:14px;line-height:1.6;color:#333;\">' + $json.notes + '</p>' : '' }}\n\n      <!-- Scope badge row -->\n      <p style='margin:0 0 8px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#888;'>Assessment scope</p>\n      <p style='margin:0 0 28px;font-size:14px;color:#555;'>{{ $json.scope_label }}</p>\n\n      <!-- Windows CTA -->\n      <p style='margin:0 0 10px;font-size:13px;font-weight:600;color:#333;'>Windows</p>\n      <table cellpadding='0' cellspacing='0' style='margin-bottom:20px;'>\n        <tr>\n          <td style='background:#1a3de4;border-radius:6px;'>\n            <a href='{{ $json.installer_windows }}' style='display:inline-block;padding:12px 28px;font-size:14px;font-weight:600;color:#ffffff;text-decoration:none;'>Download Windows Installer (.exe)</a>\n          </td>\n        </tr>\n      </table>\n\n      <!-- macOS CTA -->\n      <p style='margin:0 0 10px;font-size:13px;font-weight:600;color:#333;'>macOS</p>\n      <table cellpadding='0' cellspacing='0' style='margin-bottom:20px;'>\n        <tr>\n          <td style='border:1.5px solid #1a3de4;border-radius:6px;'>\n            <a href='{{ $json.installer_mac }}' style='display:inline-block;padding:11px 28px;font-size:14px;font-weight:600;color:#1a3de4;text-decoration:none;'>Download macOS Installer (.pkg)</a>\n          </td>\n        </tr>\n      </table>\n\n      <p style='margin:0 0 28px;font-size:12px;color:#999;'>Linux installer also available — reply to this email if you need the shell script.</p>\n\n      <!-- What it does -->  \n      <table width='100%' cellpadding='0' cellspacing='0' style='background:#f7f6f3;border-radius:8px;margin-bottom:28px;'>\n        <tr>\n          <td style='padding:20px 24px;'>\n            <p style='margin:0 0 12px;font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#888;'>What this agent does</p>\n            <p style='margin:0 0 8px;font-size:14px;color:#444;line-height:1.65;'>&bull; Collects hardware and software inventory</p>\n            <p style='margin:0 0 8px;font-size:14px;color:#444;line-height:1.65;'>&bull; Checks patch and security compliance status</p>\n            <p style='margin:0 0 8px;font-size:14px;color:#444;line-height:1.65;'>&bull; Maps network topology and open ports</p>\n            <p style='margin:0;font-size:14px;color:#444;line-height:1.65;'>&bull; Does <strong>not</strong> make changes to your systems — read-only audit only</p>\n          </td>\n        </tr>\n      </table>\n\n      <p style='margin:0 0 6px;font-size:13px;color:#888;'>This installer link expires on <strong style='color:#555;'>{{ $json.installer_expiry_label }}</strong>.</p>\n      <p style='margin:0 0 28px;font-size:13px;color:#888;'>Questions? Reply to this email or contact {{ $json.bdr_name }} directly at {{ $json.bdr_email }}.</p>\n\n    </td>\n  </tr>\n\n  <!-- Footer -->\n  <tr>\n    <td style='padding:20px 40px;border-top:1px solid #e5e3df;'>\n      <p style='margin:0;font-size:12px;color:#aaa;'>YourMSP &middot; 123 Main St, Your City &middot; <a href='#' style='color:#aaa;'>Unsubscribe</a></p>\n    </td>\n  </tr>\n\n</table>\n\n</td></tr>\n</table>\n</body>\n</html>",
        "options": {}
      },
      "id": "send-prospect-email",
      "name": "Send Email — Prospect IT Contact",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2,
      "position": [1560, 200],
      "credentials": {
        "smtp": {
          "id": "smtp-credential",
          "name": "SMTP"
        }
      },
      "notes": "Sends branded HTML email to the prospect IT contact with all three installer links. Configure SMTP credentials in n8n and set FROM_EMAIL env var."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SLACK_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"*New RMM deployment initiated* :rocket:\",\n  \"blocks\": [\n    {\n      \"type\": \"header\",\n      \"text\": { \"type\": \"plain_text\", \"text\": \"RMM Deployment Initiated\" }\n    },\n    {\n      \"type\": \"section\",\n      \"fields\": [\n        { \"type\": \"mrkdwn\", \"text\": \"*Prospect:*\\n{{ $json.company_name }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*IT Contact:*\\n{{ $json.contact_name }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*BDR:*\\n{{ $json.bdr_name }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Devices (est):*\\n{{ $json.estimated_devices }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Scope:*\\n{{ $json.scope_label }}\" },\n        { \"type\": \"mrkdwn\", \"text\": \"*Link expires:*\\n{{ $json.installer_expiry_label }}\" }\n      ]\n    },\n    {\n      \"type\": \"context\",\n      \"elements\": [\n        { \"type\": \"mrkdwn\", \"text\": \"Datto site UID: `{{ $json.datto_site_uid }}`\" }\n      ]\n    }\n  ]\n}",
        "options": {}
      },
      "id": "notify-slack",
      "name": "Notify Slack — BDR Channel",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1560, 420],
      "notes": "Posts a summary to your BDR Slack channel. Set SLACK_WEBHOOK_URL env var. Use n8n's Slack node instead if you want thread replies when devices check in."
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={ \"status\": \"ok\", \"message\": \"Deployment initiated for {{ $('Validate & Normalize Payload').first().json.company_name }}\", \"site_uid\": \"{{ $('Extract Site UID').first().json.datto_site_uid }}\" }",
        "options": {
          "responseCode": 200
        }
      },
      "id": "webhook-response",
      "name": "Respond to Portal",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1780, 300],
      "notes": "Returns a 200 OK to the BDR portal so it can show the success screen."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": false },
          "conditions": [
            {
              "leftValue": "={{ $json.statusCode }}",
              "rightValue": 200,
              "operator": { "type": "number", "operation": "notEquals" }
            }
          ]
        }
      },
      "id": "check-site-created",
      "name": "Site Created OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [900, 160],
      "notes": "Branches on Datto API error. True = error, False = success."
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={ \"status\": \"error\", \"message\": \"Failed to create Datto site. Check n8n logs.\" }",
        "options": { "responseCode": 500 }
      },
      "id": "error-response",
      "name": "Error Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1120, 100],
      "notes": "Returns an error to the portal if Datto site creation fails."
    }
  ],
  "connections": {
    "Webhook — BDR Portal Submit": {
      "main": [[ { "node": "Validate & Normalize Payload", "type": "main", "index": 0 } ]]
    },
    "Validate & Normalize Payload": {
      "main": [[ { "node": "Datto RMM — Create Site", "type": "main", "index": 0 } ]]
    },
    "Datto RMM — Create Site": {
      "main": [[ { "node": "Extract Site UID", "type": "main", "index": 0 } ]]
    },
    "Extract Site UID": {
      "main": [[ { "node": "Datto RMM — Get Agent Installer URL", "type": "main", "index": 0 } ]]
    },
    "Datto RMM — Get Agent Installer URL": {
      "main": [[ { "node": "Extract Installer Links", "type": "main", "index": 0 } ]]
    },
    "Extract Installer Links": {
      "main": [
        [ { "node": "Send Email — Prospect IT Contact", "type": "main", "index": 0 } ],
        [ { "node": "Notify Slack — BDR Channel", "type": "main", "index": 0 } ]
      ]
    },
    "Send Email — Prospect IT Contact": {
      "main": [[ { "node": "Respond to Portal", "type": "main", "index": 0 } ]]
    },
    "Notify Slack — BDR Channel": {
      "main": [[ { "node": "Respond to Portal", "type": "main", "index": 0 } ]]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "errorWorkflow": "",
    "timezone": "America/New_York"
  },
  "meta": {
    "instanceId": "your-n8n-instance",
    "templateId": "datto-rmm-prospect-deploy-v1"
  },
  "_setup_notes": {
    "credentials_required": [
      "Datto RMM API — httpBasicAuth (API key as username, secret as password)",
      "SMTP — your outbound mail server credentials"
    ],
    "environment_variables": [
      "DATTO_RMM_DOMAIN — your Datto tenant domain, e.g. yourcompany.centrastage.net",
      "FROM_EMAIL — sending address, e.g. noreply@yourmsp.com",
      "SLACK_WEBHOOK_URL — incoming webhook URL for your BDR Slack channel"
    ],
    "datto_api_notes": [
      "Enable API access in Datto RMM under Setup > API Keys",
      "The agent installer URL endpoint may vary by Datto version — verify against your API docs at https://rmm.datto.com/help/en/Content/0GITSTARTEDGUIDE/APIv2.htm",
      "The 'Extract Installer Links' node may need property name adjustments depending on your Datto version's response schema"
    ],
    "optional_enhancements": [
      "Add a Datto webhook listener workflow that fires when devices first check in, to send a follow-up Slack notification to the BDR",
      "Connect HubSpot/Salesforce node after Slack to advance deal stage automatically",
      "Add a scheduled cleanup workflow that deletes PROSPECT-tagged sites older than 30 days"
    ]
  }
}
