Hymma.ZeroBounce 1.1.0

Prefix Reserved
dotnet add package Hymma.ZeroBounce --version 1.1.0
                    
NuGet\Install-Package Hymma.ZeroBounce -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hymma.ZeroBounce" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hymma.ZeroBounce" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Hymma.ZeroBounce" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Hymma.ZeroBounce --version 1.1.0
                    
#r "nuget: Hymma.ZeroBounce, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Hymma.ZeroBounce@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Hymma.ZeroBounce&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Hymma.ZeroBounce&version=1.1.0
                    
Install as a Cake Tool

Hymma.ZeroBounce

⚠️ DISCLAIMER: This is NOT an official ZeroBounce package. It is a community-maintained .NET client library created by HYMMA. For the official SDK and support, visit ZeroBounce.

A modern .NET 8 client for the ZeroBounce email validation API (v2). Validate addresses before you send so you stop paying for bounces with your sender reputation.

Why not the official SDK?

The official ZeroBounce.SDK is a static singleton with callback-style methods that block on .Wait() internally. Inside an ASP.NET Core request that is sync-over-async: it ties up a thread-pool thread per call and can starve the pool under load. It also targets netstandard2.0, depends on Newtonsoft.Json, has no dependency-injection story, and never passes ZeroBounce's own timeout parameter.

This library is async end to end, built on IHttpClientFactory, registered with one line, uses System.Text.Json, and tells ZeroBounce how long it may spend on each address.

Features

  • Single validation — ValidateAsync, with optional sign-up IP for enrichment
  • Batch validation — ValidateBatchAsync, chunked at the API limit, request order preserved, nothing silently dropped
  • Credit balance — GetCreditsAsync
  • Typed verdicts — every status and sub_status ZeroBounce documents, as enums, with the raw strings kept alongside
  • Three-way partition — IsSafeToSend / IsConfirmedUndeliverable / IsInconclusive, so "we don't know" is never mistaken for "bad"
  • Fail-open by default — network trouble comes back as Status = Error, never as a thrown exception, unless you ask for ThrowOnError
  • Caller cancellation is honoured — your CancellationToken propagates as OperationCanceledException; it is never reported as a ZeroBounce failure
  • Regional endpoints — global, U.S. and EU base URLs as constants
  • EmailValidationException — a ready-made exception carrying the verdict, for callers that refuse a send and want "bad recipient" distinguishable from "mail service down"
  • Key stays out of URLs — every request carries the API key in a POST body, so HttpClientFactory's request-URI log lines and OpenTelemetry's url.full never contain it

Installation

dotnet add package Hymma.ZeroBounce

Quick start

1. Register

// API key directly
builder.Services.AddZeroBounce("your_api_key");

// From configuration — reads the "ZeroBounce" section
builder.Services.AddZeroBounce(builder.Configuration);

// Full control
builder.Services.AddZeroBounce(options =>
{
    options.ApiKey = "your_api_key";
    options.BaseUrl = ZeroBounceOptions.EuBaseUrl;
    options.TimeoutSeconds = 10;
});
// appsettings.json (for the configuration overload)
{
  "ZeroBounce": {
    "ApiKey": "your_api_key",
    "TimeoutSeconds": 10
  }
}

2. Validate

public class SignupService(IZeroBounceClient zeroBounce)
{
    public async Task<SignupOutcome> SignUpAsync(string email, string clientIp, CancellationToken ct)
    {
        var result = await zeroBounce.ValidateAsync(email, clientIp, ct);

        // Block only on what ZeroBounce is sure about.
        if (result.IsConfirmedUndeliverable)
        {
            var hint = result.DidYouMean is { } fix ? $" Did you mean {fix}?" : "";
            return SignupOutcome.Rejected($"We can't deliver to {email}.{hint}");
        }

        // Unknown / Error: ZeroBounce could not reach a verdict (or we could not
        // reach ZeroBounce). Let the sign-up through — a false reject costs you a
        // real customer; a bounce costs you a little reputation.
        if (result.IsInconclusive)
            logger.LogWarning("ZeroBounce inconclusive for {Email}: {Status}/{Sub}", email, result.Status, result.SubStatus);

        return SignupOutcome.Accepted;
    }
}

Verdicts

ZeroBounce returns a status and a sub_status. The library maps both to enums and partitions every status into exactly one bucket:

Status IsSafeToSend IsConfirmedUndeliverable IsInconclusive Meaning
Valid ✅ Deliverable mailbox
CatchAll ✅ Domain accepts any local part; mailbox unverifiable
Invalid ⛔ Will bounce — mailbox missing, full, bad syntax, no DNS…
SpamTrap ⛔ Known trap. Never send
Abuse ⛔ Known complainer. Never send
DoNotMail ⛔ Disposable, toxic, role-based or globally suppressed
Unknown ❓ Mail server didn't answer (greylisting, anti-spam, timeout)
Error ❓ The request itself failed (network, timeout, bad response, rejected key)

Sub-status shortcuts on the result:

result.IsMailboxFull      // mailbox_quota_exceeded — soft-bounces until the owner cleans up
result.IsMailboxNotFound  // mailbox_not_found
result.IsDisposable       // disposable
result.IsToxic            // toxic
result.IsRoleBased        // role_based or role_based_catch_all
result.HasBadSyntax       // failed_syntax_check
result.TimedOut           // timeout_exceeded — raise TimeoutSeconds if you see this a lot
result.SubStatus          // the full enum
result.RawSubStatus       // the exact string, for anything the enum doesn't know yet

Other fields: Account, Domain, DidYouMean, IsFreeEmail, MxFound, MxRecord, SmtpProvider, IsCatchAllDomain, DomainAgeDays, ProcessedAt, ErrorMessage, RawResponse.

Batch validation

var batch = await zeroBounce.ValidateBatchAsync(leads.Select(l => l.Email), ct);

foreach (var r in batch.Results)          // one per input address, same order
    if (r.IsConfirmedUndeliverable) await leads.SuppressAsync(r.Email, r.SubStatus);

if (batch.Errors.Any(e => e.AppliesToAll)) // rejected key / no credits
    logger.LogError("ZeroBounce batch failed: {Error}", batch.Errors[0].Message);

Requests are sent in chunks of 200. Blank entries are skipped. An address ZeroBounce does not answer for comes back as Status = Error rather than disappearing, so Results.Count always matches the non-blank input count.

Timeouts

ZeroBounce accepts a per-request timeout; when it runs out the API answers unknown / timeout_exceeded instead of leaving you hanging. TimeoutSeconds (3–60, default 30) is sent on every single check, and BatchTimeoutSeconds (10–120, default 120) on every batch. The HTTP call itself is allowed a few seconds more than that, so a server-side timeout arrives as a real verdict rather than a dropped connection.

Pick TimeoutSeconds by where the call sits: a sign-up form that blocks on it wants ~8–10s; a background re-validation job can afford the full 60.

Error handling

Default — fail open

var result = await client.ValidateAsync(email);

if (result.Status == EmailValidationStatus.Error)
    // network error, timeout, unparsable body, or the API said
    // {"error":"Invalid API key or your account ran out of credits"}
    logger.LogError("ZeroBounce unavailable: {Message}", result.ErrorMessage);

The one deliberate exception is GetCreditsAsync, which always throws — there is no honest fallback value for a balance.

Strict — ThrowOnError = true

try
{
    var result = await client.ValidateAsync(email);
}
catch (ZeroBounceException ex) when (ex.Code is "timeout" or "http_error")
{
    // transient — retry or fail open
}
catch (ZeroBounceException ex) // "api_error", "parse_error", "invalid_api_key"
{
    // configuration problem — page someone
}

Options

services.AddZeroBounce(options =>
{
    options.ApiKey = "…";                               // required
    options.BaseUrl = ZeroBounceOptions.DefaultBaseUrl; // or UsBaseUrl / EuBaseUrl
    options.TimeoutSeconds = 30;                        // 3–60, sent to the API
    options.BatchTimeoutSeconds = 120;                  // 10–120, sent to the API
    options.ThrowOnError = false;                       // true → ZeroBounceException instead of Status = Error
    options.IncludeRawResponse = false;                 // true → RawResponse on single-validation results
});

API quirks this library absorbs

Observed against the live API, so you don't have to:

  • Errors come back as HTTP 200 — {"error": "…"} on validate, {"errors":[{"email_address":"all",…}]} on validatebatch, and {"Credits":"-1"} on getcredits.
  • validate and getcredits take the key in a form-encoded POST body (so it stays out of your request logs); a JSON body gets 415 / 500 and a header gets a Cloudflare 403. validatebatch is JSON-only.
  • mx_found is the string "true"/"false"; free_email is a real boolean; catchall_domain may be null.
  • Credits and domain_age_days are strings (domain_age_days may be "").
  • processed_at is "yyyy-MM-dd HH:mm:ss.fff" with no zone designator (UTC).

API reference

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 92 10/6/2026
1.0.0 97 10/6/2026