A blank clipboard is the large shape, with a clock with no numerals apart from it.

Real-time email verification: should the signup form wait?

THE MAILCHECKUP JOURNAL

A signup form should wait on real-time email verification only when two things are true: the check can finish inside a budget you chose, and the answer is either broken syntax or a finished permanent reject. A timeout or an unknown result is not an invalid address. Let the person continue, store the unresolved state, and confirm they control the inbox in a separate step.

MAILCHECKUP / BLOG / REAL TIME EMAIL VERIFICATION↘

This is for a product or growth person deciding whether the submit button sits still while a check runs. You can use the decision if you never buy MailCheckup. Product facts below are limited to what the public site said on 6 October 2026.

Key takeaways

  • Block the form for local syntax problems, and for a permanent reject that actually finishes before your wait budget expires.
  • If the budget expires, or the finished code is unknown, greylisted, blocked, unreachable, or a system error, keep the address out of the invalid bucket.
  • Ownership confirmation is a different question. A mailbox accept does not prove the person at the keyboard controls that inbox.
  • On the production MailCheckup MCP host, verify_email and verify_emails are refused when live stations are required. The tool tells the agent to use a list. verify_emails caps at 25.
  • The numerical example later in this piece is illustrative. It is not a MailCheckup statistic.

What real-time means on a signup form

Real-time email verification, for this decision, means a check tied to one person still looking at the form. Should that person wait before the account exists?

The person's clock runs from submit until the next screen works. Your handler's clock is the deadline you code. The mail server's clock is not yours. RFC 5321 says an SMTP client must time out per command, and that the minimum for RCPT should be five minutes. A form cannot wait that long. A budget of a second or two will often expire while the receiving server is still within that allowance.

An interactive check

An interactive check holds the next screen until you return a message or hit the budget. Use it for a missing @, an empty domain, or a finished reject the person can retype. Holding the button on "we are not sure" gives them nothing to fix.

A background check

A background check renders the next page and stores the submitted string, the account id, the start time, and unresolved. When a code arrives, suppress mail or ask for a different address. The person is not the retry loop.

A list job

A file, a paste, or an agent with many addresses is a list. Run it as a list. Do not poll that job from the submit handler and call the wait real-time.

The decision tree

Use these questions in order. The first match is the decision. The diagram repeats the same branches.

Decision tree: interactive block, warn, or background continuation for a signup email check
Ask in order. A timeout or unknown continues. Ownership confirmation stays a separate step.

Is a person waiting on this screen?

If nobody is waiting, this is a list job: an import, a nightly pass, a backlog of unconfirmed trials, or an agent with a spreadsheet. On the production MailCheckup MCP host, verify_email and verify_emails are refused when live stations are required, and the tool tells the agent to use a list. verify_emails caps at 25. The refusal is about that host, not only about the cap, so a short batch is still a list. If a person is on the form, continue. One address in front of a human does not make a refused tool answer inside the click.

Can you answer without contacting a mail server?

Yes for broken shape. RFC 5322 defines an addr-spec as a local string, an @, and a domain. Reject a missing @, a blank domain, or a string of spaces in the browser. Keep the typed value on screen.

MailCheckup treats broken syntax as invalid. A typo suggestion stays separate, and the submitted address is not rewritten. The person has to accept a suggestion. A local disposable-domain list, if you keep one, can also answer without SMTP. Blocking throwaway domains on a free trial can be reasonable. Blocking them on an enterprise billing contact can drop the only address a buyer will give you. Role addresses such as info@ are classifications, not broken syntax. A consumer app and a B2B app should not share one role rule. If nothing local settled the address, you still do not know whether a mailbox exists. Continue.

Did a permanent reject finish inside the budget?

Set a wait budget, B. When it expires, release the person even if a socket is still open.

A permanent reject is a finished mailbox refusal, or syntax the server rejected. RFC 5321 section 4.2.1 uses 5yz replies for that case: the command was not accepted, and the identical request should not be repeated. MailCheckup maps a reject to undeliverable, dead, or invalid. Those finished results cost one credit. If one arrives before B, ask for a correction and name the field. Catch-all, disposable, role account, and mailbox full are not that reject. Warn, or continue and suppress mail, under a rule you wrote down. Do not call those codes invalid.

Is the result unknown, late, or simply absent?

If B expires and you have no finished code, the result is absent. The UI stopped waiting. The address has not been shown to be invalid. Record unresolved, release the button, and reconcile when a code arrives.

If the finished code is unknown, the server did not give a clear yes or no. MailCheckup uses unknown for a greylist, a block, a timeout, or no clear answer. Retry later, greylisted, mail server unreachable, provider blocked, and system error are the related free outcomes. System error means the check did not finish and the hold is released. Not checked means no finished result yet. None of these is invalid.

RFC 5321 section 4.2.1 calls a 4yz reply a transient negative: the action did not occur, and the client should try again. A timeout is not even that. There is no reply, so there is no 5yz permanent reject either. Section 7.3 says a site that disables VRFY must return 252, which must not be mistaken for a verified or unverified mailbox, and that RCPT may return no validity information when the server defers that decision until after DATA. Inconclusive is a normal outcome of the receiving system.

On this branch, continue in the background. Create the account or the lead, mark the check unresolved, and do not tell the person the address is fake.

Will you confirm ownership anyway?

If the product needs to know this person can open the mailbox, send a confirmation link or code after the form advances. That step does not depend on a deliverable code. An unknown address can still belong to the person who typed it. A deliverable address can be someone else's inbox. Run ownership on the continue and warn branches. On a permanent reject, ask for a new address first, then confirm that new address.

Why a timeout is not an invalid address

A syntax failure is local, and the person can fix it. A recipient reject is a finished negative. RFC 5321 defines RCPT as the command that names an individual recipient. A refusal is evidence about this attempt only. A timeout is a clock: your budget fired, or the checker recorded no clear answer. MailCheckup's public check order says greylist, block, timeout, or no clear answer means unknown, and unknown is not invalid. The note on unknown versus invalid states that split. It does not decide whether the button waits.

What "deliverable" is allowed to mean

Deliverable means the mailbox server accepted the recipient during the check. It does not mean inbox placement, a reader, or permission to write to them. Gmail's sender guidelines tell mailers to automatically unsubscribe recipients who have multiple bounced messages (Email sender guidelines). That covers mail you already sent. An unresolved signup check is not a bounce.

Null MX is not a timeout

RFC 1035 defines an MX as a preference plus a host willing to exchange mail. RFC 7505 defines null MX: preference 0 and exchange ., a published statement that the domain accepts no email. That is different from a lookup that never returned. MailCheckup's check order treats "no mail server, or one that cannot be reached" as inconclusive, not as a confident invalid person. This article does not invent a null-MX code beyond that order. A finished domain-level failure inside the budget can use the permanent-reject branch. A slow lookup cannot.

Classifications that are not the invalid bucket

Disposable, role account, catch-all, and mailbox full are MailCheckup's risky summary. Risky is not a separate charge and not a synonym for invalid. Each of those codes costs one credit when finished. The inconclusive codes are free. Catch-all accepts recipients without identifying one mailbox. Treat it with its own rule. "Same as invalid" refuses real company domains and still misses abuse on domains that answer one mailbox at a time.

Ownership confirmation is a separate step

Verification asks whether the address looked operable at a point in time. Ownership asks whether this session can open the mailbox. Consent asks whether you may keep mailing them. Store the three answers in different fields.

A confirmation link proves that someone who could read the mailbox also held the link before it expired. It does not prove a legal name, inbox placement, or that a permanent reject was wrong. If the mailbox refuses mail, the link never arrives. Learn that after the account exists, or after a retype. Do not learn it by freezing the form for an SMTP conversation.

Order for a typical trial:

  1. Fix syntax in the form, with no network check.
  2. If you run a network check, hold the button only until B, and block only on a finished permanent reject.
  3. Create the account in a limited state.
  4. Send the ownership challenge to the submitted address, unchanged.
  5. Widen the account when the challenge succeeds.
  6. When a background code arrives, apply suppression. Do not delete the ownership event because the code is unknown. Do not treat deliverable as permission to skip the challenge.

The limited state is what makes the background path safe. The person cannot invite a company, send campaigns, or spend money until ownership succeeds, if that is the risk you care about. If a new account can mail thousands of people immediately, gate that send. Providers that refuse probes will still return unknown for legitimate customers.

What the production MCP host will refuse during a click

MailCheckup's executable agent interface is the MailCheckup MCP server: server name mailcheckup, endpoint https://cp.mailcheckup.net/mcp. Callers use a workspace API key shown once in portal Settings. It starts with mck_. This article does not include a live key.

verify_email and verify_emails exist. On this production host they are refused when live stations are required. The tool tells the agent to use a list. The cap on verify_emails is 25. Anything larger is a list. That is the public MCP page as reviewed on 6 October 2026.

A refused tool is not a mailbox verdict. If the form treats the refusal, an HTTP 401, or {"error":"<code>"} as an invalid address, you are rejecting the person because the path is closed.

Why the cap does not rescue an interactive design

The cap of 25 is not a latency promise and not permission to call verify_email once per signup. The refusal applies when live stations are required, including to the one-address tool.

Addresses you already have go through a list: account_status first, because a run does not start at zero credits; create_list, which does not verify; add_emails up to 500; start_verification; then poll get_list outside the tool. New rows join the same run. Do not open a second list for them. Remove and delete are refused while the run is busy. That job does not belong in the submit handler.

The public page on API principles is not a finished REST contract. Idempotency-key format, REST base URL, and REST request schema are not finalized there. This article does not name a signup route, a JSON field, or an authorization header for a REST verify call. If a later contract publishes a single-address request with a defined timeout, apply the same tree to its response codes. Until that text exists, do not code the form against a path you made up in a ticket.

How this differs from the SaaS page and the API page

The page on email verification for SaaS applications already states the product stance in brief: check at a moment you choose, keep ownership separate, do not reject every unknown, and do not let a slow provider trap the user. It does not give the branch order, the split between "no result yet" and "result is unknown," or a worked comparison of wait budget against an inconclusive share.

The page on email verification API principles tells a developer to separate transport errors from verdicts, to bound the wait, and to confirm ownership with a link. It also says the production API contract has to define authentication, idempotency, and billable outcomes before an integration is enabled. It does not operate the go/no-go for a submit button, and it does not document the MCP refusal of verify_email / verify_emails. Using that page as if it were a live signup API is how teams invent endpoints.

This article is the decision those pages do not calculate: branch order, the split between "no result yet" and "unknown," and a worked comparison of wait budget against an inconclusive share. The MCP page is the tool constraint. None of the three is a measured inconclusive rate.

Illustrative example: wait budget versus inconclusive share

Every percentage and count in this section is an assumption. None of it is a MailCheckup measurement, a customer story, or a benchmark. Replace each input with your own logs before you argue about a vendor. The diagram repeats the same partition so the arithmetic can be checked in a review.

Illustrative partition of 5,000 signup attempts by syntax, permanent reject, inconclusive, and other finished results
Illustrative assumptions only. Bar length matches each share of 5,000. Not a MailCheckup measurement.

Inputs, all illustrative

  • N = 5,000 signup attempts in the window you care about.
  • B = 1.2 seconds. This is a product wait budget, not an RFC timeout, and not a published MailCheckup latency.
  • s = 0.015 of attempts fail local syntax before any network check. S = 75.
  • p = 0.030 finish inside B as a permanent reject (undeliverable, dead, or invalid). P = 150.
  • u = 0.160 are still unresolved when B expires, or finish as unknown, retry later, greylisted, mail server unreachable, provider blocked, or system error. U = 800.
  • The rest, f = 0.795, finish inside B as some other code (deliverable, catch-all, disposable, role, or mailbox full). F = 3,975.

Check the sum: 0.015 + 0.030 + 0.160 + 0.795 = 1. 75 + 150 + 800 + 3,975 = 5,000. The example does not split F further. It does not claim how many of those 3,975 were deliverable.

Policy A: refuse anything that is not a finished non-reject

Policy A holds every attempt for up to B and treats syntax failures, permanent rejects, and the inconclusive bucket as "bad email." Refusals = 75 + 150 + 800 = 1,025.

Of those 1,025 refusals, 800 had neither a syntax failure nor a permanent reject. 800 / 1,025 = 78.0% of Policy A refusals in this illustration. That slice is the cost of mapping "we do not know" to "invalid."

Summed waiting is not a measured total. If all 5,000 attempts consumed the full 1.2 seconds, the sum is 6,000 seconds (100 minutes) spread across those people. If only the 800 inconclusive attempts used the full budget, the sum is 960 seconds (16 minutes). Both figures assume a clock this sheet does not have. There is no latency distribution here, so you cannot place your form inside that gap.

Policy B: block only what the person can fix or what was actually rejected

Policy B blocks the 75 syntax failures immediately and the 150 permanent rejects that finish inside B. The 800 unresolved attempts continue with the check marked unresolved. The other 3,975 follow a written rule per code. They are not dropped into a marketing audience. The gap between A and B is those 800 people. Policy B does not approve their addresses. It refuses to call them invalid.

Support time, still illustrative

If 10% of the 800 contact support under Policy A, and a contact takes 8 minutes, that is 80 contacts and 640 minutes (10 hours 40 minutes). The contact rate is an assumption:

Illustrative contact rate among the 800ContactsMinutes at 8 each
2%16128
10%80640
25%2001,600

The table is not a forecast. The expensive bucket is the inconclusive one. The permanent-reject catch stays 150 while p stays fixed.

If the inconclusive share moves

Hold N, s, and p still. Change only u.

Illustrative uU at N = 5,000Share of Policy A refusals that are inconclusive
0.05250250 / 475 = 52.6%
0.16800800 / 1,025 = 78.0%
0.301,5001,500 / 1,725 = 87.0%

Policy A refusals are 475 at u = 0.05 and 1,725 at u = 0.30. Permanent rejects stay at 150 while p is fixed. Moving B from 1.2 seconds to 4 seconds changes nothing here: there is no arrival-time curve. A longer spinner lowers U only if your logs show rejects landing in the extra seconds. Extra time that only turns "absent" into "unknown" is a longer wait for the same action.

Credits, using the published rule on these assumptions

A finished chargeable result costs one credit. Unknown, retry later, greylisted, unreachable, provider blocked, and system error cost nothing, and an unfinished hold is released. In this illustration the 75 local syntax failures cost zero credits if they never leave the browser, the 150 permanent rejects cost 150 credits if MailCheckup finishes them, and the 800 free inconclusive codes cost 0. Calling those 800 invalid does not save a charge you did not incur. The sheet is not an invoice, and it does not itemize the other 3,975 because it does not split them.

What the form should do with each finished code

When a background result arrives, update a status field. Do not overload a boolean named email_valid.

Finished situationForm action while the person is presentLater account action
Local syntax failureBlock. Show the typed value.Do not create the account on that string.
Invalid, undeliverable, or dead, inside the budgetBlock and ask for a correction.Do not send the ownership mail to that string.
Same reject, arriving after you already continuedDo not kick a confirmed owner out with a generic failure page.Stop outbound mail. Ask for a new address in product.
Deliverable or validDo not skip ownership.Send the confirmation. Deliverable is not inbox placement and not consent.
Catch-all, disposable, role, mailbox fullDo not use invalid copy. Warn only if the policy says so.Apply the policy you wrote for that code. One credit each if MailCheckup finished them.
Unknown, retry later, greylisted, unreachable, provider blocked, system errorContinue. These are not invalid.Leave unresolved or retry. Do not delete the row. On a list, unknown can be rechecked without redoing rows that already have a result.
No result yet, or the budget expiredContinue. Absent is not invalid.Reconcile when a code exists.
Tool or transport error, including a refused verify_emailContinue, or show a generic retry.Do not copy {"error":"<code>"} into the invalid flag.

A late undeliverable code, after the person has already confirmed the link, is a conflict to review. Stop outbound mail and ask for a new address. A later unknown must not clear that ownership event.

Form action by result class: block, warn, or continue
Block on syntax and on a finished permanent reject. Continue on unknown, timeout, and tool errors.

Acceptance checks for the spec

These are tests for your form. They are not a claim that MailCheckup ships a sandbox with these strings.

  • not-an-email blocks, and the typed value stays visible. No network call is required.
  • A plausible address whose client wait exceeds B renders the next screen with state unresolved, not invalid.
  • A finished permanent reject inside B blocks, with correction copy, and does not send the ownership message to that string.
  • Unknown, greylisted, provider blocked, and a timeout recorded as unknown all continue. None sets invalid.
  • A transport failure or {"error":"list_busy"} leaves the invalid flag unset.
  • Ownership completed during unresolved stays set when a later unknown arrives. A later permanent reject stops outbound mail and prompts for a new address. It does not delete the account.
  • A double submit creates one account and one pending check. The account id is your idempotency key. Do not depend on an unpublished REST header.
  • Signup does not call verify_email or verify_emails and then block on the refusal.

Leave the raw address out of analytics. Count syntax blocks, reject blocks, unresolved continues, and completed ownership challenges in your own warehouse. Those names are not MailCheckup webhook events. email.unknown on the webhook contract is a list pipeline, not this form.

Failure modes that look like a strict form

If B is a thirty-second gateway timeout, people refresh and you create two accounts. Set the deadline before you argue about verdicts.

Storing unknown as invalid makes activation fall and fills support with people on domains that greylist probes. Split the flag.

One reject sentence for syntax, disposable, and role hides the fix. A free-trial disposable block needs its own copy, from a local list when you have one, so the button does not wait on SMTP. A role address on a company form often needs no warning. Unknown is not a disposable rule.

Calling list tools from submit fails in a different way. create_list does not start verification. Creating a list, adding one address, starting a run, and polling get_list will hit list caps and list_busy, and it will miss B. Move the check off the click.

Treating deliverable as ownership lets anyone who can type another person's address hold the account. A late unknown must not delete a user who already confirmed the link. A late permanent reject stops outbound mail and asks for a new address. Keep the submitted string, the time, the code, and the policy version. A stored reject is not a mailing list, because verification is not consent.

What to measure after the rule ships

On the same form, record attempts, syntax blocks, permanent-reject blocks, and unresolved continues. Record ownership challenges sent, completed, and expired. Record support contacts a person tagged as "signup said my email was invalid." Record hard bounces on the first product mail after ownership, divided by messages you actually sent. Time from submit to next paint comes from your telemetry. This article has no median and no 95th percentile. Leave the raw address out of analytics. A rule that cuts bounces and also cuts completed ownership can be a poor trade. If unresolved continues are rare in your logs, Policy A refuses fewer people, and Policy B can still be the default, because a timeout is the wrong thing to call invalid.

Next step

Write B down, name the three states (syntax failure, permanent reject, unresolved), and ship Policy B unless your own denominators say the inconclusive share is tiny and you have a support path for the people you still refuse by mistake. For addresses you already collected, where no one is staring at a button, run them as a list in the app rather than as a click-time call the production MCP host refuses.

Open the MailCheckup app and check an existing set of addresses there as a list. The marketing site does not run that check. A list result still will not prove inbox placement or ownership. The confirmation link remains your step.

RELATED ARTICLES

More from the journal.

An open door with a blank interior is the large shape, with a short stack of blank cards apart from it.Deliverability & Hygiene
19 min read

Email verification fundamentals before a campaign

An email marketer with permission to mail still needs a way to separate sendable rows from fixes, reviews, and holds. This is the operating checklist and the before-and-after process for that decision.

Read article
A reply slip already moving away is the large shape, with a solid token with no digits apart from it.Webhooks & APIs
19 min read

Email verification webhooks: ack fast, apply once

A receiver design for developers who take MailCheckup list events on their own HTTPS URL: signature on the raw body, a stored evt_ id, an 8-second ack, and an action for all 10 published events.

Read article
A short stack of blank cards is the large shape, with an open door with a blank interior apart from it.MCP & AI Agents
19 min read

Email verification MCP: run a list without blocking the agent

An operator's sequence for the MailCheckup MCP server: read credits, load one list in chunks of 500, poll without blocking, leave unknown in place, and wait for the export email.

Read article
A building with one mail slot is the large shape, with a short stack of blank cards apart from it.Protocols & Syntax
19 min read

What an SMTP accept response proves about an email address

SMTP verification answers whether a mailbox server accepted a recipient during a check. This guide shows how to read that reply, and how to map accept, reject, defer, and timeout onto categories you can operate.

Read article
A building with no mail slot is the large shape, with a building with one mail slot apart from it.Protocols & Syntax
19 min read

What an MX lookup can prove when a website already loads

A loading website proves that a web server answered. An MX lookup proves only the mail-exchange data DNS returned. This guide separates those facts from the assumptions people attach to them, including implicit MX, null MX, and an unreachable server.

Read article
A chute with one exit is the large shape, with a thin slip pulled out on its own apart from it.Catch-All & Disposable
19 min read

Catch-all email results: what to keep, review, or suppress

When a list comes back catch-all, deleting the segment and marking it valid are both the wrong default. Acceptance proves domain policy. Your records decide keep, review, or suppress.

Read article
A thin slip pulled out on its own is the large shape, with a shallow empty tray apart from it.Catch-All & Disposable
18 min read

Disposable email detection policy: block, review, or allow by workflow

Disposable is a classification, not proof of fraud. Block it as the only recovery path, step up a trial, send the receipt, keep it off the newsletter, and allow a low-risk download. The cost sketch counts waste and legitimate users. Every volume in it is illustrative.

Read article
An open door with a blank interior is the large shape, with a balance with one empty pan apart from it.Billing & Credits
15 min read

How published plan rates and credits fit a list run

Match a list run to published credits, list caps, row caps, and the rate column. Unknown is free. A run does not start when available credits are zero.

Read article