---
title: "How to Measure Signups After an Email Typo Correction"
description: "Report completed signups after accepted email corrections with /v1/outcomes, stable UUID v4 retries, and a server-side completion boundary instead of counting suggestion impressions."
slug: "measure-signups-after-email-correction"
date: 2026-10-13
updated: 2026-10-13
last_tested: 2026-10-13
summary: "A displayed suggestion is not a recovered signup. Report only an accepted correction followed by completed registration, keep the outcome identifier stable, and distinguish observed completion from causal lift."
cluster: "Signup flows"
intent: how-to
sources:
  - title: "nobounce.dev agent reference — customer outcomes"
    url: "https://nobounce.dev/llms.txt"
  - title: "nobounce.dev OpenAPI specification"
    url: "https://nobounce.dev/openapi.json"
  - title: "nobounce.dev verdict taxonomy and pricing"
    url: "https://nobounce.dev/v1/config"
  - title: "Node.js crypto — randomUUID"
    url: "https://nodejs.org/api/crypto.html#cryptorandomuuidoptions"
  - title: "RFC 9457 — Problem Details for HTTP APIs"
    url: "https://www.rfc-editor.org/rfc/rfc9457.html"
---

To measure signups after email correction, report an outcome only when the user accepted the suggestion and registration actually completed. nobounce exposes `POST /v1/outcomes` for that customer-reported event. Send the fixed outcome name and a random UUID v4, reuse that identifier on retries, and keep reporting outside the registration response's critical path.

Counting suggestions alone measures detection. Counting clicks measures acceptance. Neither tells you whether an account was created, and none establishes how many registrations would have been lost without the correction.

## Define the completion boundary first

Your application owns the meaning of completed signup. For a simple registration handler, it might mean the account transaction committed. If your product requires a confirmation step before registration is complete, use that later boundary consistently. Do not change definitions halfway through a comparison.

Track these stages separately in your own application:

| Stage | Evidence | Report to /v1/outcomes? |
|---|---|---|
| Suggestion available | Check returned a non-null suggestion | No |
| Suggestion displayed | Current form rendered it | No |
| Correction accepted | User explicitly chose the suggested value | Not yet |
| Registration completed after acceptance | Your completion boundary was reached using the accepted correction | Yes |
| Registration abandoned or failed | No completed registration | No |

The correction can arrive with `undeliverable`, `risky`/`likely_typo`, or an `unknown` result that still carries a suggestion. Offer any non-null suggestion before branching on rejection. The existing [React verdict walkthrough](https://nobounce.dev/blog/email-validation-verdicts-in-react-form/) describes the UI; this article concerns the completion event after that UI did its job.

## Preserve acceptance across the final submit

Record acceptance against the current signup attempt, not a global browser flag. If the user accepts a suggestion and then edits the address again, invalidate that acceptance. A stale “corrected” flag must not label an unrelated final address.

The backend should validate the attempt state against its own registration workflow. A browser-supplied `corrected: true` by itself is weak evidence. Keep enough protected local state to know which correction the user selected and whether it remained the final submitted value. Do not export that state to nobounce.

[Preventing stale validation results](https://nobounce.dev/blog/prevent-stale-email-validation-results/) covers the generation guard that prevents an obsolete suggestion from replacing newer input. Apply the same ownership rule to acceptance metadata.

Once the completion boundary is reached, create one outcome identifier. Save it with a pending reporting job. Ideally, commit the account change and that job in the same local transaction, so a process crash cannot lose the event between account creation and enqueueing.

## Send exactly two body fields

The live contract, checked on October 13, 2026, accepts this fixed outcome:

```bash
OUTCOME_ID=$(node -p 'require("node:crypto").randomUUID()')

curl -sS https://nobounce.dev/v1/outcomes \
  -H "Authorization: Bearer $NOBOUNCE_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary "{\"outcome\":\"signup_completed_after_correction\",\"outcome_id\":\"$OUTCOME_ID\"}"
```

Run this only for a real completed registration after an accepted correction. Do not send synthetic successes to production to inspect the endpoint.

The body permits no address, suggestion, domain, hash, user ID, checkout ID, or arbitrary properties. The API key belongs in the `Authorization` header, never in the body, URL, or query parameters. No `Idempotency-Key` header is needed for this contract: deduplication uses the random UUID v4 in `outcome_id`, scoped to the authenticated account.

Access requires a key obtained by redeeming an operator-minted access code or paying self-serve through Stripe cards or usevig stablecoins; both rails are live. There is no free tier. Hobby is $1/mo for 1,000 checks/mo, pro $19/mo for 100,000, and scale $99/mo for 1,500,000.

## Retry the report, not the registration

A worker can send the saved job with server-side Node.js:

```js
export async function reportCorrectionSignup(job) {
  try {
    const response = await fetch("https://nobounce.dev/v1/outcomes", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.NOBOUNCE_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        outcome: "signup_completed_after_correction",
        outcome_id: job.outcomeId,
      }),
      signal: AbortSignal.timeout(3000),
    });
    return { acknowledged: response.status === 200, status: response.status };
  } catch {
    return { acknowledged: false, status: null };
  }
}
```

Generate `job.outcomeId` once with `randomUUID()` when creating the durable job, not inside this function. A lost response may mean the event was already recorded. Reusing the UUID prevents the retry from adding another outcome; generating a new one defeats deduplication.

Mark a job acknowledged only after success. Use bounded backoff for transport failures or temporary server errors. Inspect RFC 9457 `error` and `fix` on non-success responses: malformed bodies and invalid credentials need repair, not an infinite retry loop. Reporting failure must never roll back a completed account or make the user resubmit registration.

## Name the metric honestly

Call this “customer-reported completed signups after correction,” not proven incremental conversions. The endpoint records your assertion; it does not independently observe the user's click, account transaction, mailbox access, or eventual delivery.

For local analysis, keep denominators: eligible attempts, suggestions displayed, corrections accepted, and completed registrations after acceptance. Calculate acceptance and post-acceptance completion separately. Estimating causal lift requires a suitable comparison design, not just dividing reported outcomes by API checks.

No plaintext address is persisted by nobounce; anything persisted is SHA-256 hashed, with domains in clear. Your own acceptance state, job queue, and logs still need safeguards. Log reporting status and bounded event names, not addresses or suggestions.

Test this workflow with a stubbed reporter: declined correction produces no job; accepted correction followed by failure produces none; successful completion produces one; repeated delivery attempts reuse its UUID. `/demo/check` is fixture-only and cannot validate arbitrary real addresses or simulate your account transaction.

For production key handling and the full signup verdict mapping, give your coding agent [/integrate.md](https://nobounce.dev/integrate.md).
