Skip to content
Start with 100 free verification credits
Qualisend
All articles
Guides / May 14, 2026

Verify emails from HubSpot forms in real time

9 minutes read

Qualisend team
Verify emails from HubSpot forms in real time

A HubSpot form is a contact-capture machine: every submission creates or updates a contact record, sets properties, and can enrol that contact in a workflow. What it won't do is tell you whether the address someone typed is actually real. HubSpot's email field runs a format check — it confirms there's an @ and a domain — but it can't see that the mailbox is dead, disposable, or a typo of gmail.com. This guide shows two practical ways to verify emails from HubSpot forms with Qualisend: in real time, with an Operations Hub workflow that POSTs each new address to the Qualisend API, and in bulk, by exporting contacts to a CSV. Neither needs a native connector — there isn't one yet — just HubSpot's built-in workflow actions or its export button.

The short answer#

Pick the path that matches when you need the answer:

  • Real time — enrol new form submitters in a workflow. A custom-code or webhook action calls the Qualisend API with the email, and a branch writes the verdict to a contact property, then routes or suppresses the record based on the returned status.
  • Bulk — export your contacts to CSV, run them through Qualisend's bulk verification, and suppress the undeliverables before your next send.

Why HubSpot's form field can't confirm a mailbox#

HubSpot's email field validates format — it checks that the string looks like an address before it lets the form submit. That's the same class of check a regex does, and it fails for the same reasons: format is not deliverability. jane@gmial.com passes the format check. So does throwaway@mailinator.com, and so does a real-looking address at a domain that shut down last year.

None of those are caught until you send — and by then a dead address is a hard bounce that chips at your bounce rate and sender reputation. Real verification adds the layers HubSpot can't: a DNS/MX lookup and an SMTP mailbox probe, plus flags for disposable, role, and catch-all addresses. If that pipeline is new to you, what email verification actually checks is the primer.

Two ways to verify emails from HubSpot forms#

The important constraint first: a standard HubSpot form can't block a bad email mid-submit. There's no supported way to call an external API between the "Submit" click and the record being created, so real-time verification happens just after submission, inside a workflow — not as an inline gate. That shapes both paths below.

PathWhen it runsBest for
Real-time workflowSeconds after each submissionLive intake — tag, route, or hold follow-up before the first send
Bulk CSVOn demand, in batchesExisting contact backlogs and periodic list hygiene

Path 1: real-time with an Operations Hub workflow#

Build a contact-based workflow and set the enrolment trigger to "Form submission", scoped to the form you care about. Every new submitter now flows into the workflow, where you make the verification call and act on the result.

There are two ways to make that call — pick the one that fits your portal and your stack.

Option A — a custom-code action (self-contained). If you're on Operations Hub Professional or Enterprise, a custom-code action runs Node.js (or Python) right inside the workflow. Add the enrolled contact's email as an input property, store your key as a secret named QUALISEND_API_KEY, and call Qualisend from the action:

// HubSpot custom-code action (Node.js) — input property: email, secret: QUALISEND_API_KEY
const QUALISEND_ENDPOINT = "https://app.qualisend.com/api/v1/verify"; // confirm the path in /developers

exports.main = async (event, callback) => {
  const email = event.inputFields.email;

  // Fail open — never block a real lead on a transient API error.
  const failOpen = () => callback({ outputFields: { email_status: "unknown" } });

  let res;
  try {
    res = await fetch(QUALISEND_ENDPOINT, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.QUALISEND_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(10000),
    });
  } catch {
    return failOpen(); // timeout or network error
  }
  if (!res.ok) return failOpen(); // 402 out of credits, 429 rate limited, 5xx

  const { job_id, result } = await res.json();
  callback({
    outputFields: {
      email_status: result.status, // deliverable / risky / undeliverable / unknown
      email_score: result.score,
      email_smtp: result.smtp, // pending / cached / skipped
      qualisend_job_id: job_id,
    },
  });
};

The action exposes email_status, email_score, email_smtp and qualisend_job_id as outputs. When email_smtp is cached or skipped, the verdict is already final. When it's pending, the mailbox probe is still running and email_status is only the instant local verdict (usually unknown), so don't route on it yet: branch on email_smtp, add a short Delay, then a second custom-code action that reads the finished job, with qualisend_job_id as its input:

// Second custom-code action, after the Delay: input qualisend_job_id, secret: QUALISEND_API_KEY
exports.main = async (event, callback) => {
  const jobId = event.inputFields.qualisend_job_id;

  try {
    const res = await fetch(`https://app.qualisend.com/api/v1/jobs/${jobId}?include=results`, {
      headers: { Authorization: `Bearer ${process.env.QUALISEND_API_KEY}` },
      signal: AbortSignal.timeout(10000),
    });
    const job = res.ok ? await res.json() : null;
    const row = job?.status === "completed" ? job.results?.[0] : null;
    if (row) {
      return callback({ outputFields: { email_status: row.status, email_score: row.score } });
    }
  } catch {
    // timeout or network error: fall through and fail open
  }

  // Still running, or the API didn't answer: keep it unknown and re-check later.
  callback({ outputFields: { email_status: "unknown" } });
};

On both branches, follow the last action with a Copy property value step that writes email_status into a custom contact property, then use an if/then branch to route on it: the whole reason you ran the check.

Option B — a webhook action to your own function. Prefer to keep the logic in your own codebase, or not on a custom-code tier? Add a webhook action that POSTs to a small serverless function you control. The function pulls the email, calls Qualisend, and writes the final verdict back to the contact with HubSpot's CRM API (when smtp comes back pending, it reads that verdict from GET /jobs/{job_id} once the job finishes). It's the same server-side shape as the verify-at-signup pattern: the API key never leaves the server, and you act on the result. The endpoint code in the Node.js walkthrough or the Python walkthrough drops almost straight in — only the payload parsing is HubSpot-specific.

Either way, the underlying request is the same, and the placeholders are yours to fill from the developer docs:

POST https://app.qualisend.com/api/v1/verify
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

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

A deliverable address comes back like this — a status, a 0–100 score, a reason, and a set of sub_flags:

{
  "job_id": "6f1d2c3e-8a4b-4c5d-9e6f-7a8b9c0d1e2f",
  "result": {
    "status": "deliverable",
    "score": 95,
    "smtp": "cached",
    "reason": "accepted_email",
    "sub_flags": { "disposable": false, "role": false, "free_mail": false, "catch_all": false }
  }
}

Here smtp is cached, so the verdict is already final. When it's pending instead, reason is still null and the final status, score and reason come from the job lookup.

Two rules keep this robust. Use a dedicated key minted just for this workflow (the default verify scope covers both the check and the job lookup), so if it ever leaks from a log you can revoke it without breaking anything else. And fail open: if Qualisend times out or the plan limit is hit, treat the address as unknown and keep the contact rather than dropping a real lead over a transient blip: that's exactly what the catch and !res.ok branches above do.

Downstream, the verdict is just a branch. Set a custom "Email status" property from the output, then let your workflow decide: a deliverable contact continues into the nurture sequence; an undeliverable or disposable one gets the follow-up held back, gets flagged for review, or is set to non-marketing so it's excluded from active lists and sends. The workflow is the plumbing; the routing is yours.

Path 2: bulk-verify a CSV export#

If you already have thousands of contacts, or you'd rather clean on a schedule than run per-submission checks, go in batches — and this route needs no workflow, so it works below the Operations Hub tiers too. In HubSpot, open Contacts, pick a list or filter, and Export to CSV. You'll get one row per contact with the email in its own column.

Then:

  1. Upload the CSV to Qualisend's bulk verification and let it run the full pipeline — MX, SMTP, and the disposable/role/catch-all flags — across every row.
  2. Download the annotated file. Each address comes back with a status, a reason, and a 0–100 score.
  3. Suppress the undeliverables back in HubSpot: import the results to set your "Email status" property, then exclude anything that isn't deliverable from your active lists and marketing sends.

Bulk is the right tool for a backlog, but it's cleanup, not prevention: an address you catch here has usually already been sitting in your database. Pair a periodic bulk pass with the real-time workflow so new bad addresses are flagged the moment they arrive.

Acting on the verdict#

Whichever path returns the result, the shape is the same — a status, a reason, a score, and the sub-flags. Map each to an action:

ResultWhat to do in HubSpot
deliverableAccept — continue the workflow and send normally.
undeliverableSuppress. Set the contact to non-marketing or exclude it from sends; check the reason to see whether it's a bad domain or a dead mailbox.
disposable flagReject or tag, depending on how strict your funnel is — see role, disposable & free addresses.
risky / catch-allAccept but segment. A catch-all domain can't confirm the individual mailbox, so send cautiously.
unknownAccept and re-check later — the infrastructure didn't answer, so never drop a real lead over it.

If you're weighing HubSpot's workflow approach against calling the API directly from your own backend, the same trade-off applies as with any glue: workflows win on speed-to-set-up, a direct integration wins on cost and control at scale. The same webhook pattern also powers the sibling guides for Zapier and Typeform if some of your intake lives outside HubSpot.

Frequently asked questions

01Is there a native Qualisend integration for HubSpot?

Not right now. Qualisend's native platform integrations are being rebuilt, so there's no branded app to install in the HubSpot Marketplace yet - it's on the roadmap. Until it ships, the supported way to verify emails from HubSpot forms is a workflow custom-code or webhook action pointed at the Qualisend API, exactly as this guide describes. That approach is also more flexible: you control the request, the branching, and which contact property the verdict lands in.

02Do I need Operations Hub to verify emails from HubSpot forms?

For the real-time workflow, effectively yes. Workflows require a Professional tier, and the webhook and custom-code actions specifically require Operations Hub Professional or Enterprise. If your portal is on a lower tier, use the CSV export and bulk-verify route instead - it needs no workflow and no custom code, just an export and a file upload, so it works on any plan that lets you export contacts.

03Can I block a bad email before the form is submitted?

Not reliably. A standard HubSpot form has no supported way to call an external API mid-submit and stop the record from being created, so real-time verification runs just after submission, inside the workflow. In practice that's fine: you verify within seconds and act before the address ever receives a message - you hold the welcome email, tag the contact, or route it to review rather than blocking the form itself.

04How is this different from cleaning my existing HubSpot list?

Verifying at intake stops new bad addresses from entering; cleaning your existing list removes the ones already in it. This guide is about the first - catching each submission as it lands. For the second, you're doing a one-off (or scheduled) pass over your whole database, deciding what to suppress and how to re-engage stale contacts. The full routine, including where each status belongs, is in how to clean an email list. Most teams do both.


Ready to wire it up? Grab a scoped key and the exact request format in the developer docs, sanity-check a single address in the free email checker, or start with the 100 free credits on every plan to test the workflow end to end before you turn it on.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started