← All Analytics Workflows
BillionVerifyEvidence

Collect SOC 2 AWS IAM evidence to Google Sheets with Gmail alerts

Pull contacts, verify each address with BillionVerify, and continue to Evidence β€” only deliverable addresses get through.

Why verify before the send

Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the Evidence step removes that risk automatically β€” only deliverable addresses continue, the rest are flagged.

The workflow

BillionVerify β€” verification sits right before the send.

Node by node

  1. 1
    Quarterly ScheduleTriggerΒ· n8n

    Starts the workflow β€” on a schedule, a webhook, or manually while you test.

  2. 2
    Set Audit DataSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  3. 3
    List IAM UsersSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  4. 4
    Format User EvidenceSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  5. 5
    Users Found?LogicΒ· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  6. 6
    Export to Google SheetsSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  7. 7
    Verify Email (BillionVerify) 2VerifyΒ· billionverify

    The BillionVerify node verifies the address β€” status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score β€” before anything is sent.

  8. 8
    Summarize RunSourceΒ· n8n

    Provides or transforms the contact data flowing through the workflow.

  9. 9
    IF deliverable 2LogicΒ· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  10. 10
    Verify Email (BillionVerify)VerifyΒ· billionverify

    The BillionVerify node verifies the address β€” status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score β€” before anything is sent.

  11. 11
    Send Warning EmailSendΒ· n8n

    Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.

  12. 12
    IF deliverableLogicΒ· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  13. 13
    Send Success EmailSendΒ· n8n

    Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.

Workflow JSON

Copy or download this workflow, then import it in n8n (Workflows β†’ Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.

verify-emails-in-evidence.json
{
  "name": "Collect SOC 2 AWS IAM evidence to Google Sheets with Gmail alerts + BillionVerify",
  "nodes": [
    {
      "id": "b4dec251-3751-411d-ae2f-553ca9da5c0e",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3872,
        2160
      ],
      "parameters": {
        "width": 444,
        "height": 472,
        "content": "## SOC 2 Evidence Collector \n### How it works\n1. **Trigger**: Runs automatically every quarter (or manually).\n2. **Collect**: Fetches active IAM User Directory via native AWS node.\n3. **Logic**: Verifies if user data was successfully retrieved.\n4. **Export**: Appends the directory to a Google Sheet.\n5. **Notify**: Sends an executive summary email to the admin.\n\n### Setup steps\n- [ ] Connect AWS Credentials (`iam:ListUsers`)\n- [ ] Connect Google Sheets Account\n- [ ] Create a Google Sheet named 'IAM Access Review'\n- [ ] Add columns: Username, User ID, ARN, Create Date, Audit Date\n- [ ] Connect Gmail account to both Email nodes\n- [ ] Update 'Send To' email addresses\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "fc238ca2-16a6-4659-85e0-86c0d7be4ef8",
      "name": "Sticky Note Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4352,
        2160
      ],
      "parameters": {
        "color": 7,
        "width": 424,
        "height": 480,
        "content": "### 1. TRIGGER & CONFIG\nSchedule β†’ Timestamp\nInitiates the quarterly audit and tags \nthe run with a precise timestamp."
      },
      "typeVersion": 1
    },
    {
      "id": "95833ffb-4d0a-4525-87be-34841e68e014",
      "name": "Sticky Note Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4832,
        2160
      ],
      "parameters": {
        "color": 7,
        "width": 404,
        "height": 480,
        "content": "### 2. AWS IAM COLLECTION\nFetch β†’ Format\nRetrieves the active AWS IAM User \nDirectory for User Access Reviews."
      },
      "typeVersion": 1
    },
    {
      "id": "4467920b-13d4-4a90-88fc-8d8de13e145e",
      "name": "Sticky Note Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5264,
        2160
      ],
      "parameters": {
        "color": 7,
        "width": 620,
        "height": 480,
        "content": "### 3. LOGIC & EXPORT\nBranch β†’ Log β†’ Aggregate\nRoutes data based on success. Appends \nto Sheets and summarizes the total count."
      },
      "typeVersion": 1
    },
    {
      "id": "088593ca-0c72-4131-9738-be66e5018d3f",
      "name": "Sticky Note Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5920,
        2160
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 480,
        "content": "### 4. NOTIFICATIONS\nEmail Delivery\nSends professional alerts for both \nsuccessful audits and warnings."
      },
      "typeVersion": 1
    },
    {
      "id": "9ee8a30b-7541-4f8b-ad69-d8dc9305adbc",
      "name": "Quarterly Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        4416,
        2384
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 1 */3 *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "e3f876c4-8933-4a10-9dda-7a871bef448c",
      "name": "Set Audit Data",
      "type": "n8n-nodes-base.code",
      "position": [
        4640,
        2384
      ],
      "parameters": {
        "jsCode": "return {\n  json: {\n    auditType: 'User Access Review (UAR)',\n    collectionTimestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "af226c77-16df-4767-a19d-1724aeef2632",
      "name": "List IAM Users",
      "type": "n8n-nodes-base.awsIam",
      "onError": "continueRegularOutput",
      "position": [
        4864,
        2384
      ],
      "parameters": {
        "requestOptions": {},
        "additionalFields": {}
      },
      "typeVersion": 1
    },
    {
      "id": "faa2591d-9d1f-46e5-ad8f-ba3a50baff43",
      "name": "Format User Evidence",
      "type": "n8n-nodes-base.code",
      "position": [
        5088,
        2384
      ],
      "parameters": {
        "jsCode": "const inputData = $input.all();\nconst auditData = $('Set Audit Data').first().json;\n\nlet users = [];\nif (inputData.length > 0 && inputData[0].json && inputData[0].json.Users) {\n    users = inputData[0].json.Users;\n} else if (inputData.length > 0 && inputData[0].json && inputData[0].json.UserName) {\n    users = inputData.map(item => item.json);\n}\n\nif (users.length === 0) {\n  return [{ json: { username: 'NO_USERS_FOUND', auditDate: auditData.collectionTimestamp } }];\n}\n\nconst formatted = users.map(user => ({\n  json: {\n    username: user.UserName || 'N/A',\n    userId: user.UserId || 'N/A',\n    arn: user.Arn || 'N/A',\n    createDate: user.CreateDate || 'N/A',\n    auditDate: auditData.collectionTimestamp\n  }\n}));\n\nreturn formatted;"
      },
      "typeVersion": 2
    },
    {
      "id": "be83c2fb-d60b-4a66-86e8-8279f402e591",
      "name": "Users Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        5312,
        2384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.username }}",
              "rightValue": "NO_USERS_FOUND"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "c6cddc58-555c-44d3-b970-3cd38b07c9f7",
      "name": "Export to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        5536,
        2288
      ],
      "parameters": {
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "a3783b1f-be0f-4993-9d8a-3f82c52d70cf",
      "name": "Summarize Run",
      "type": "n8n-nodes-base.code",
      "position": [
        5760,
        2288
      ],
      "parameters": {
        "jsCode": "const totalCount = $input.all().length;\nconst auditDate = $('Set Audit Data').first().json.collectionTimestamp;\nreturn [{ json: { totalUsers: totalCount, auditDate: auditDate.split('T')[0] } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "8fcd1f6d-8db8-4892-b1c7-1d85a720e822",
      "name": "Send Success Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5984,
        2288
      ],
      "webhookId": "b9250838-5c4e-4a9e-9434-b69f4af32c22",
      "parameters": {
        "message": "=<div style=\"font-family: 'Segoe UI', Arial, sans-serif; color: #1f2937; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden;\"><div style=\"background-color: #059669; padding: 25px; color: #ffffff; text-align: center;\"><h1 style=\"margin: 0; font-size: 22px;\">AWS Access Review Complete</h1></div><div style=\"padding: 30px; background-color: #ffffff;\"><p style=\"font-size: 16px;\">Hello,</p><p style=\"font-size: 15px; color: #4b5563;\">Your quarterly AWS IAM User Access Review has been automatically executed and the evidence has been logged for SOC 2 compliance.</p><div style=\"background-color: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px; margin: 25px 0; text-align: center;\"><p style=\"margin: 0; font-size: 14px; color: #6b7280; text-transform: uppercase;\">Active IAM Users Exported</p><h2 style=\"margin: 10px 0 0 0; color: #059669; font-size: 36px;\">{{ $json.totalUsers }}</h2></div><p style=\"font-size: 14px; color: #4b5563;\"><strong>Audit Date:</strong> {{ $json.auditDate }}</p><p style=\"font-size: 14px; color: #4b5563;\">All user details have been appended to your connected Google Sheet.</p></div><div style=\"background-color: #f3f4f6; padding: 15px; text-align: center; font-size: 11px; color: #9ca3af;\"><p style=\"margin: 0;\">Automated by n8n β€’ SOC 2 Evidence Collector</p></div></div>",
        "options": {},
        "subject": "βœ… SOC 2 Audit: AWS Access Review Complete"
      },
      "typeVersion": 2.2
    },
    {
      "id": "8d7beb4e-e725-47b9-bcc1-c4dd600fcb21",
      "name": "Send Warning Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5984,
        2480
      ],
      "webhookId": "693529d4-d576-4e81-84ce-c3927ca1bb35",
      "parameters": {
        "message": "=<div style=\"font-family: 'Segoe UI', Arial, sans-serif; color: #1f2937; max-width: 600px; margin: 0 auto; border: 1px solid #fecdd3; border-radius: 12px; overflow: hidden;\"><div style=\"background-color: #e11d48; padding: 25px; color: #ffffff; text-align: center;\"><h1 style=\"margin: 0; font-size: 22px;\">⚠️ Audit Warning</h1></div><div style=\"padding: 30px; background-color: #ffffff;\"><p style=\"font-size: 16px;\">Hello,</p><p style=\"font-size: 15px; color: #4b5563;\">Your scheduled AWS IAM User Access Review just ran, but <strong>no users were found</strong> or the connection failed.</p><div style=\"background-color: #fff1f2; border: 1px solid #fecdd3; border-radius: 8px; padding: 20px; margin: 25px 0;\"><h3 style=\"margin: 0 0 10px 0; color: #be123c; font-size: 16px;\">Troubleshooting Steps:</h3><ul style=\"margin: 0; padding-left: 20px; font-size: 14px; color: #4b5563;\"><li>Check your AWS credentials in n8n.</li><li>Ensure the IAM user running this has the `iam:ListUsers` permission.</li></ul></div><p style=\"font-size: 14px; color: #4b5563;\">Please resolve this issue before your next auditor check-in.</p></div><div style=\"background-color: #f3f4f6; padding: 15px; text-align: center; font-size: 11px; color: #9ca3af;\"><p style=\"margin: 0;\">Automated by n8n β€’ SOC 2 Evidence Collector</p></div></div>",
        "options": {},
        "subject": "⚠️ SOC 2 Audit Warning: No AWS Users Found"
      },
      "typeVersion": 2.2
    },
    {
      "parameters": {
        "operation": "verify",
        "email": "={{ $json.email || $json.Email }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-billionverify.billionVerify",
      "typeVersion": 1,
      "position": [
        5624,
        2288
      ],
      "name": "Verify Email (BillionVerify)",
      "credentials": {
        "billionVerifyApi": {
          "id": "",
          "name": "BillionVerify account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-deliverable",
              "leftValue": "={{ $json.is_deliverable }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        5804,
        2288
      ],
      "name": "IF deliverable"
    },
    {
      "parameters": {
        "operation": "verify",
        "email": "={{ $json.email || $json.Email }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-billionverify.billionVerify",
      "typeVersion": 1,
      "position": [
        5624,
        2480
      ],
      "name": "Verify Email (BillionVerify) 2",
      "credentials": {
        "billionVerifyApi": {
          "id": "",
          "name": "BillionVerify account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-deliverable",
              "leftValue": "={{ $json.is_deliverable }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        5804,
        2480
      ],
      "name": "IF deliverable 2"
    }
  ],
  "connections": {
    "Users Found?": {
      "main": [
        [
          {
            "node": "Export to Google Sheets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Verify Email (BillionVerify) 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarize Run": {
      "main": [
        [
          {
            "node": "Verify Email (BillionVerify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List IAM Users": {
      "main": [
        [
          {
            "node": "Format User Evidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Audit Data": {
      "main": [
        [
          {
            "node": "List IAM Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quarterly Schedule": {
      "main": [
        [
          {
            "node": "Set Audit Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format User Evidence": {
      "main": [
        [
          {
            "node": "Users Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export to Google Sheets": {
      "main": [
        [
          {
            "node": "Summarize Run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Email (BillionVerify)": {
      "main": [
        [
          {
            "node": "IF deliverable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF deliverable": {
      "main": [
        [
          {
            "node": "Send Success Email",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Verify Email (BillionVerify) 2": {
      "main": [
        [
          {
            "node": "IF deliverable 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF deliverable 2": {
      "main": [
        [
          {
            "node": "Send Warning Email",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  }
}

When to use this

  • Cleaning a list before a Evidence send or sync.
  • Protecting Evidence deliverability and sender reputation.
  • Keeping bounce rates low so your sending is never throttled.

FAQ

Why verify before sending in Evidence?

Verifying first keeps your bounce rate low, which protects your sender reputation and your results.

How do I import this workflow?

Download the JSON, then in n8n go to Workflows β†’ Import from File (or paste it). Install the BillionVerify community node and add your API key credential.

What happens to risky or catch-all addresses?

They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.

Add verification to your workflow

Create a free account, grab your API key, and stop bounces before they happen.

Get started free