emailverifier.dev
Posts

Email Verification in n8n: An Importable Signup Workflow

| 4 min read | Usama Ejaz
n8n email verification cover with four connected workflow nodes and one envelope.

This n8n workflow turns a webhook into a small signup decision service. It receives an email address, makes one authenticated request to emailverifier.dev, maps the result, and answers the original webhook with allow, review, or block.

Signup Webhook -> Verify Email -> Map Decision -> Return Signup Decision

There is no database node in the workflow. Your application should create the account only after it receives allow.

Import the four-node workflow

Save the JSON below as verify-signup-email.json, open n8n, and choose Import from File from the workflow menu.

{
  "name": "Verify signup email",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "verify-signup-email",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "9d883c5d-9093-4cb3-9ef7-7d15a31400b8",
      "name": "Signup Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0],
      "webhookId": "verify-signup-email"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://emailverifier.dev/api/v1/verify",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [{
            "name": "X-API-Key",
            "value": "={{ $env.EMAILVERIFIER_API_KEY }}"
          }]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ email: $('Signup Webhook').item.json.body.email }) }}",
        "options": { "timeout": 4000 }
      },
      "id": "a4bb53a2-36fb-4852-95f9-6dcc0fb202fd",
      "name": "Verify Email",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [240, 0]
    },
    {
      "parameters": {
        "jsCode": "const report = $input.first().json;\n\nconst statusCode = report.action === 'block' ? 422 : report.action === 'review' ? 202 : 200;\n\nconst message = report.action === 'block' ? (report.suggestion ? 'Check the spelling of your email address.' : 'Use a different email address.') : report.action === 'review' ? 'Confirm this address before receiving full access.' : 'Continue to account creation.';\n\nreturn [{ json: { statusCode, decision: report.action, message, signals: report.signals, suggestion: report.suggestion } }];"
      },
      "id": "f0460e17-0da7-474f-a42a-0bf5b0714a2b",
      "name": "Map Decision",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [480, 0]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": { "responseCode": "={{ $json.statusCode }}" }
      },
      "id": "49fe148c-bf8f-48cb-8c16-c3cb37f227f5",
      "name": "Return Signup Decision",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [720, 0]
    }
  ],
  "connections": {
    "Signup Webhook": {
      "main": [[{"node":"Verify Email","type":"main","index":0}]]
    },
    "Verify Email": {
      "main": [[{"node":"Map Decision","type":"main","index":0}]]
    },
    "Map Decision": {
      "main": [[{"node":"Return Signup Decision","type":"main","index":0}]]
    }
  },
  "pinData": {},
  "settings": { "executionOrder": "v1" },
  "active": false
}

The workflow uses n8n’s core Webhook, HTTP Request, Code, and Respond to Webhook nodes. Keeping the mapping in one Code node makes the imported artifact readable and avoids three nearly identical response branches.

Provide the API key without putting it in the export

Create an emailverifier.dev account and project, then copy its API key. Add it to the n8n process environment as EMAILVERIFIER_API_KEY and restart n8n.

export EMAILVERIFIER_API_KEY="your-project-key"
n8n start

The HTTP Request node reads $env.EMAILVERIFIER_API_KEY. The secret is not embedded in the workflow JSON or returned by the webhook. If your n8n deployment blocks node access to environment variables, replace the header expression with an n8n Header Auth credential named for this project.

Test the workflow before activating it

Open Signup Webhook, select Listen for test event, and copy its test URL. Submit:

curl -X POST "YOUR_N8N_TEST_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"email":"person@example.com"}'

One execution should show one item moving through all four nodes. The HTTP Request node sends one request with this body:

{"email":"person@example.com"}

The webhook response uses:

  • 422 block for a confirmed failure or blocking signal.
  • 202 review for an inconclusive result that should proceed through confirmation or restricted access.
  • 200 allow for the normal account-creation path.

The upstream verification statuses are deliverable, risky, undeliverable, and unknown. The Code node maps the separate action field instead of inventing policy from the status string.

Handle node failures deliberately

The imported workflow stops if the HTTP Request node times out or receives an API error. During testing, verify that your caller handles a failed or timed-out webhook as a retryable dependency failure.

For a production workflow that must always answer, add a node-level error workflow that returns 503. Do not turn a timeout, invalid API key, exhausted credit balance, or rate limit into 422 block. The API result-handling guide separates HTTP failures from address decisions.

After the test execution passes, activate the workflow and use the production webhook URL from your signup service. Protect that URL with your normal application authentication or an unguessable internal route. The webhook is a backend integration point, not a public email-checking form.

Continue reading