Handling results

Four verdicts, one switch. Every response carries suggested_action with this exact mapping, so the first integration is already correct; the reasons are there when you want finer policy.

verdictsuggested_actionWhy
deliverableacceptThe mailbox answered. Let the user through.
undeliverablerejectNo mail server, or the server rejected the mailbox permanently, or the address is malformed. Show the reason in plain words: "that domain can't receive email", "that mailbox does not exist".
riskyaccept_and_confirmA fact you cannot act on alone: the domain accepts every address, the mailbox is full, or it is disposable. Let the user through and confirm by email. The disposable case is the exception: suggested_action says reject, because the mailbox is real and the user is not staying.
unknownaccept_and_confirmThe server would not say (a "come back later", a block on our IP, a tarpit) or the time ran out. Not billed. Let the user through and confirm by email, or retry later; never reject a real person on a maybe.

The switch

switch (data.verdict) {
  case 'deliverable':   return accept(email);
  case 'undeliverable': return reject(email, data.reason);  // e.g. "no_mail_server"
  case 'risky':                                              // catch-all, full mailbox, disposable
  case 'unknown':                                            // the server would not say; not billed
    return data.suggested_action === 'reject' ? reject(email, data.reason) : acceptAndConfirm(email);
}

Typos

When did_you_mean is not empty, offer it as a one-click correction before anything else: Did you mean sara@gmail.com? It is only ever a free-provider domain within two characters of what was typed, so it is never wrong by much.

Flags for your own policy

flags.disposable, flags.role_account and flags.free_provider are facts, not verdicts. A B2B lead form may reject free providers; a newsletter may not care. A free-trial signup rejects disposable addresses; a support form should not. Decide once, in your code.

When you cannot wait

Pass timeout (milliseconds, 1000–45000). If it runs out you get unknown with the reason timeout, unbilled, and the switch above already handles it. Two seconds is a reasonable ceiling for a signup form; a lead form that posts in the background can afford the full 45.

Explaining a rejection

Add evidence=true when you log verifications. When a user writes in, the rcpt_to reply in the evidence is what their mail server said, verbatim, and it settles the question in seconds.