All posts

Verify Emails in a Pipedream Workflow: HTTP Trigger and a Node.js Code Step

··7 min read

To verify emails in Pipedream, start a workflow with an HTTP / Webhook trigger, add a Node.js code step that sends the address to an email verification API, and end the run early for anything that is not valid. Keep the key in a secret environment variable, answer the sender immediately, and let the verification happen after the response so a slow mail server never blocks a form.

This guide builds that flow with the SimpleVerifier API. Pipedream is a good fit when you want code and a hosted trigger with no server, for example to sit between a form tool and your CRM. If you prefer a visual builder, we have guides for n8n and Make, and the Zapier, Make or webhook guide compares them.

What you need

  • A SimpleVerifier plan with API access: Pro, Business or Agency. Pro is $39 a month. It includes 10,000 API calls a month, then $1.50 per 1,000 on monthly billing. Pro email verification also runs the first 100,000 checks a month at full speed, then slower, never cut off. See pricing.
  • An API key from the API keys page. It starts with sv_live_ and is shown once.
  • A Pipedream account, and something that can send a webhook with an email field (a form tool, your app, or curl).

The endpoint is GET or POST https://simpleverifier.com/api/v1/verify with email and a Bearer key. The full reference is in the API docs.

Step 1: Store the key

In Pipedream, open Settings, then Environment Variables, and choose New Variable. Name it SIMPLEVERIFIER_KEY and paste the key. Secret is on by default, so the value is never shown in the UI again. You can also scope variables to a project from the project's Variables page.

Pipedream's docs give two cautions worth repeating. Names are case-sensitive, and process.env only works inside the component's run function. And anything you console.log ends up in the step's logs, so never log the key.

Step 2: The trigger

Create a workflow and pick HTTP / Webhook as the trigger. Pipedream gives you a URL. Whatever JSON is sent to it appears at steps.trigger.event.body. Test it with curl, replacing the URL with yours:

curl -X POST "https://YOUR-ENDPOINT.m.pipedream.net" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@company.com", "name": "Jane", "source": "demo-form"}'

Your form tool or app should send the same shape. Select the event in Pipedream so the later steps can see the fields.

Step 3: The verification code step

Add a Node.js code step. Every Pipedream code step uses defineComponent with an async run. Paste this.

import axios from "axios";

const ENDPOINT = "https://simpleverifier.com/api/v1/verify";

export default defineComponent({
  async run({ steps, $ }) {
    await $.respond({
      immediate: true,
      status: 200,
      body: { received: true },
    });

    const lead = steps.trigger.event.body ?? {};
    const email = String(lead.email ?? "").trim().toLowerCase();

    if (!email.includes("@")) {
      return $.flow.exit("No usable email in the payload");
    }

    const verdict = await verify(email, $);

    $.export("verdict", verdict);
    $.export("lead", { ...lead, email });

    if (verdict.action !== "send") {
      return $.flow.exit(`Not sending: ${verdict.status} (${verdict.reason})`);
    }
    return verdict;
  },
});

async function verify(email, $) {
  for (let attempt = 0; attempt < 3; attempt++) {
    const response = await axios.get(ENDPOINT, {
      params: { email },
      headers: { Authorization: `Bearer ${process.env.SIMPLEVERIFIER_KEY}` },
      timeout: 30000,
      validateStatus: () => true,
    });

    if (response.status === 200) return response.data;
    if (response.status === 401) throw new Error("401: API key missing, wrong or revoked");
    if (response.status === 402) throw new Error("402: plan has no API access, or the yearly quota is used");
    if (response.status === 429) {
      const wait = Number(response.headers["retry-after"]) || 30;
      await new Promise((resolve) => setTimeout(resolve, wait * 1000));
      continue;
    }
    if (response.status === 503) {
      await new Promise((resolve) => setTimeout(resolve, 2000 * (attempt + 1)));
      continue;
    }
    throw new Error(`Verification API answered ${response.status}`);
  }
  throw new Error("Verification API still unavailable after 3 tries");
}

Walking through it:

  • The response goes first. $.respond with immediate: true answers the sender, then the rest of the workflow keeps running. Without it Pipedream answers when the workflow ends, and if it is never called at all the endpoint returns a 400 with "No $.respond called in workflow", according to Pipedream's trigger docs. Keep the call at the very top.
  • A 30 second timeout. A live mailbox check waits on the recipient's mail server and can take close to 30 seconds. A shorter timeout turns slow answers into errors.
  • Retries are explicit. A 429 waits the number of seconds in Retry-After (the same number is retry_after_seconds in the JSON body). A 503 backs off and tries again. Both are the documented retryable cases.
  • A 402 throws. Retrying will not help. Pipedream marks the run as failed and, by default, emails you about an unhandled error, at most one email per workflow per 24 hours. Note that errors during testing do not send emails; only live runs do.
  • 401 and other 4xx throw too, because the same request would fail again.

Pipedream also offers an auto-retry setting for failed steps. You can leave it off here because the step already retries what is retryable, and you do not want a 402 repeated.

Step 4: What happens to each status

The step exits early unless action is send, and action is only ever send for valid. Here is the full picture so you can route the other cases instead of dropping them.

status action Suggested route
valid send Continue to your CRM step
risky send_with_care Add to a review list or tag unconfirmed, hold from sequences
risky retry_later The server did not answer. Queue it for another try later
invalid do_not_send Tag and suppress, never send

To route rather than exit, replace the $.flow.exit with a branch (Pipedream's If / Else operator or your own code) that writes each class to a different place: a Google Sheet tab, an Airtable table, a CRM property. The decision fields section of the docs lists every action and reason_code, and unknown codes should be treated like other, going by action.

Step 5: The CRM step

After the code step, add the step that creates or updates the contact. Reference the verified lead as steps.nodejs.lead (rename the step first if you called it something else) and the verdict as steps.nodejs.verdict. Because the code step exited early for anything unsafe, this step only runs for valid addresses.

Store the result on the contact too, for example as a custom email_status property, so later segments can filter on it.

Testing it

  1. Send a known good address and check that the verdict shows valid and the CRM step runs.
  2. Send an address that cannot work, such as jane@gmial.com. The run should exit early with the status and reason in the log, and the CRM step should not run.
  3. Temporarily change the environment variable to a wrong key and send again. The run should fail with the 401 message.
  4. Send 130 requests in a minute, from a loop in a script, to see the 429 path. Pipedream's logs show the wait.

Test with your own addresses first. Every call is a billable API call on your plan.

Errors and fixes

Symptom Likely cause Fix
Sender gets 400 "No $.respond called" The respond call was skipped or an error came first Keep $.respond first in the step
process.env.SIMPLEVERIFIER_KEY is undefined Variable name mismatch, or used outside run Check case, move code inside run
402 on every run Starter plan, inactive subscription, or yearly quota used See errors
Regular 429s Bulk import or another tool on the same key Spread requests out, or use a CSV upload for the import
Slow runs Slow recipient mail servers Expected, the sender is not waiting

What it costs

Each lead is one call. Pro includes 10,000 calls a month, then $1.50 per 1,000 on monthly billing. On yearly billing the included calls are a fixed monthly quota, and further calls return 402 until the 1st (UTC). Business and Agency include more. Pipedream has its own plan limits, which are on their site.

The short version

HTTP trigger, a secret SIMPLEVERIFIER_KEY, a Node.js step that responds first, then verifies with a 30 second timeout, and exits early unless the action is send. Wait out 429s, stop on 402, and keep risky addresses in a review list instead of deleting them. The quickstart has a curl test for the key.

Common questions

Can Pipedream verify email addresses?

Pipedream runs the workflow, it does not check mailboxes. Add a Node.js code step that calls an email verification API, then branch on the status the API returns.

How do I keep an API key safe in Pipedream?

Create an environment variable in Settings with the Secret option on, which is the default, and read it in code with process.env. Do not paste the key into the step, and avoid console.log on it, because logged values appear in the step's logs.

What happens if my Pipedream workflow never calls $.respond?

Pipedream's documentation says the endpoint answers 400 Bad Request with the message 'No $.respond called in workflow' if $.respond is never called, for example when an error happens before it. Call it first with immediate: true so the sender always gets an answer.

What should the workflow do with a risky result?

Do not treat it as valid and do not delete the lead. Risky means delivery was not confirmed, for example a catch-all domain or a server that did not answer. Send it to a review list, or hold it back from automated sequences.

How many calls can I make a minute?

One API key can make 120 calls in a sliding minute. A 429 reply includes a Retry-After header with the seconds to wait. A workflow that fires once per lead rarely reaches that, but a burst from a bulk import can.

Check your list before you send

Real mailbox checks over SMTP, and nothing is sent to the address. Sign in with Google and your first 100 email checks are free, no card. Plans for bigger lists are on the pricing page.