## Key Takeaways

- **Data-driven validation, not guesswork.** Bird’s (formerly SparkPost’s) Recipient Validation uses billions of real delivery and engagement events to determine whether an email address is valid, risky, undeliverable, or contains a typo.
- **Smarter than syntax checks.** Unlike traditional regex-based tools, this model uses real-world data to classify deliverability and even suggests corrections with a “did_you_mean” feature.
- **Real-time integration.** Validate email addresses directly within your sign-up forms, CRMs, or bulk lists via the /recipient-validation/single/ API endpoint.
- **Developer-first examples.** Working code snippets are available in multiple programming languages — including Python, Node.js, PHP, Go, C#, Ruby, Java, Rust, and more — to make integration simple.
- **Security best practice.** API keys should always be stored server-side (never exposed in client-side code), using environment variables such as SPARKPOST_API_KEY.
- **Beyond validation.** Combine Recipient Validation with webhook consumers or Azure Functions to build scalable, serverless verification workflows for production use.
- **Continuous improvement.** Developers are encouraged to contribute examples in other languages, ensuring global accessibility across ecosystems.

## SparkPost Recipient Validation is now available both for existing SparkPost customers and for new, non-sending customers. It uses powerful data-driven analysis on billions of bounce, delivery, and engagement events daily to train our algorithm, bringing you one of the most powerful data-driven email validation tools on the market, so you can send emails smarter.

SparkPost [Recipient Validation](/developer/email/email-analytics/recipient-validation) is now available both for existing SparkPost customers and for new, non-sending customers. It uses powerful data-driven analysis on billions of bounce, delivery, and engagement events daily to train our algorithm, bringing you one of the most powerful data-driven email validation tools on the market, so you can send emails smarter. This represents the latest evolution in [email validation techniques](/blog/peeking-email-validation-techniques), moving beyond simple syntax checks to sophisticated data-driven approaches that provide more accurate results.

[This article](https://support.sparkpost.com/docs/recipient-validation/getting-started-recipient-validation) explains how you can get the most out of the data you’ll receive back on each validated recipient – you’ll see we classify addresses to be “valid”, “risky”, “neutral”, “undeliverable”, and “typo”. We give you a “reason” code and also a “did_you_mean” for known address typos.

## C\#

I’m less familiar with C# – to me, it looks quite Java-like, rather than C-like. I was able to put this together following examples shown in the request library [System.Net.Http](https://learn.microsoft.com/en-us/dotnet/api/system.net.http?view=netframework-4.8).

Postman can auto-generate example code using [RestSharp](https://web.archive.org/web/20200309072521/http://restsharp.org/), if you prefer that.

## API requests

In the SparkPost web app, you can drag & drop an entire list for validation. You can also use [the API](https://developers.sparkpost.com/api/recipient-validation/) to validate single addresses, so you can build validation right into your address entry workflow.

A while back we came up with a [Python command-line tool](https://web.archive.org/web/20221127074434/https://www.sparkpost.com/blog/recipient-validation-api-based-validation/) using this API. We talked over what we should do for other languages – and here we are! Let’s get started.

This [Github repository folder](https://github.com/SparkPost/code-snippets/tree/master/snippets/recipient-validation) has working Recipient Validation API call examples in around a dozen different languages. We try to cover the [most popular applicable languages](https://www.tiobe.com/tiobe-index/).

The common way of working through all these examples is:

- Pick up your key from environment variable `SPARKPOST_API_KEY`
- Make an API call to `/api/v1/recipient-validation/single/`to validate a recipient
- Receive back a response string, containing JSON-formatted data with the result
- Print out the result

| **Language** | **HTTP library used**    | **Where validation happens** | **Notable considerations**               |
| ------------ | ------------------------ | ---------------------------- | ---------------------------------------- |
| Bash / Curl  | curl CLI                 | Terminal-only usage          | No parsing of response JSON              |
| PHP          | curl_setopt              | Server-side                  | Multiple library options available       |
| Python       | requests                 | Script or backend apps       | Converts JSON into dict automatically    |
| Node.js      | axios (recommended)      | Server-side only             | Avoid exposing API keys client-side      |
| Go           | net/http + encoding/json | Backend services             | Strong type safety with custom structs   |
| C#           | System.Net.Http          | Server applications          | Postman can generate RestSharp version   |
| Java         | HttpURLConnection        | Server services              | Verbose but widely deployable            |
| C / C++      | libcurl + OpenSSL        | Systems-level tooling        | Manual memory safety required            |
| Lua          | luasocket + luasec       | Scripting companion          | Can efficiently stream chunked responses |
| Perl         | LWP::UserAgent           | Legacy systems               | JSON parsing optional                    |
| VB.net       | Visual Studio SDK        | Windows console apps         | Environment variable setup required      |
| Rust         | reqwest + tokio async    | Modern web services          | Async needed for header handling         |

SparkPost has [libraries](https://developers.sparkpost.com/) for some, but not all of the languages covered here. We chose to write these examples “native” instead, so we could a) cover more languages, b) show how simple the underlying code can be, and c) enable you to see clearly the similarities and differences between languages.

## Bash / Curl

This wins the prize for shortest code – it simply uses the command-line tool “curl” to make the request and print the reply directly to the terminal. You can see the output is a string, containing JSON; we don’t actually parse the individual result attributes.

## PHP

Trusty PHP has a few different ways to make HTTPS API calls. Here, we chose to use [curl_setopt](https://www.php.net/manual/en/function.curl-setopt.php) together with [curl_exec](https://www.php.net/manual/en/function.curl-exec.php).

If you prefer [HTTP_Request2](https://pear.php.net/manual/en/package.http.http-request2.intro.php) or [pecl_http](https://pecl.php.net/package/pecl_http), then Postman has a built-in code generator that you can use to create similar examples – just set up a working GET request and use the “Code” button.

## Python

This uses the popular [requests](https://requests.readthedocs.io/en/master/) module, which is high-level and therefore easy to use. This example checks the returned status code, converts the results JSON back into a Python dictionary object, and prints the resulting object rather than just a string.

If you prefer the built-in [http.client](https://docs.python.org/3/library/http.client.html) library, Postman can generate code for that too; it’s not much longer.

## Node.js

There are [many different](https://github.com/request/request/issues/3143) node.js HTTP(S) libraries. I started with the older [request](https://www.npmjs.com/package/request) package (using a callback function) but it’s deprecated and no longer actively maintained.  I chose the newer [axios](https://www.npmjs.com/package/axios) package (using [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)). 

Postman can also give you a Javascript native example and [Unirest](https://www.npmjs.com/package/unirest), in case you prefer those.

Because this code needs access to your API key, we strongly recommend calling our API from your server-side, never from the client (browser / mobile device) side.

## Go

Go strives toward a philosophy of “one good way” to do something; in this case, using the built-in “batteries included” libraries [net/http](https://pkg.go.dev/net/http), [encoding/json](https://pkg.go.dev/encoding/json) and others.

The length is due mostly to the explicit error checking clauses `if err != nil {}` everywhere ([no exceptions](https://go.dev/doc/faq#exceptions) LOL).

We also declare the results object structure with field tags, to enable us to “unmarshal” the JSON returned string. We overlay the “results” and “errors” tags to allow for both kinds of return.

I like the speed, type-safety and clarity of Go, even if the code is longer than our previous examples.

## Java

I’ve not written any serious Java before, but it was easy to piece this together by following the general approach used in the [SparkPost library](https://github.com/SparkPost/java-sparkpost) for other GET calls.

Incidentally, using VS Code as my editor / debugger worked really well for all the languages here, giving me syntax highlighting, debugger stepping / variables viewing etc. `The InputStreamReader`and `BufferedReader`constructs are similar to (and I assume were copied by) Go.

## C / C++

This was a trip down memory lane, as I wrote a lot of C code in the 1990s, some still running deep in telecoms networks somewhere. As the [history of C](<https://en.wikipedia.org/wiki/C_(programming_language)#History>) predates the modern Web, it’s not surprising that library support is a manual task. We need to download (and compile) a recent version of [Libcurl](https://curl.se/libcurl/), linking to an OpenSSL library – see the [README](https://github.com/SparkPost/code-snippets/tree/master/snippets/recipient-validation#c--c) for actual steps.

This feels like a lot of work compared to modern languages, particularly when Go (or Lua, or Python, or any of the others) are fast enough for tasks like this.

The other thing I had forgotten, despite bearing the scars from previous battles, is the scariness of memory allocation! To keep the example simple, I preallocated the URL string length as 1024 characters, and bounds-checked the email address length (using `strlen`) before we concatenate into it (using `strcat`).

We treat the Authorization string with a concatenated API key in the same way .. even though we know a valid API key will never be too long .. that’s no protection! User input coming from an environment variable could be anything. You must program defensively.

A more sophisticated developer might use `malloc`instead of stack variable allocation, and calculate just how long the joined strings need to be. Having to think about this extra complexity gave me a [pain in the diodes down my left side](https://en.wikipedia.org/wiki/Marvin_the_Paranoid_Android#Radio_and_TV_series); it reminded me of the risks that C programmers run every day, trying to avoid buffer overruns and unexpected side-effects. Which brings us to ..

## Lua

Lua is known for its easy coexistence alongside a body of C code, and here at SparkPost, we [used Lua extensively](https://web.archive.org/web/20221129085325/https://www.sparkpost.com/blog/unleashing-lua/) for Policy customisations inside our Momentum on-premises MTA. You can also use it as a stand-alone scripting language, and it’s pretty nice for that, too.

With Lua 5.3 and the [luarocks](https://luarocks.org/) package manager, we use libraries `luasocket` and `luasec`. Showing its C integration heritage, we link to our local OpenSSL library. The luarocks install process calls the `gcc` compiler (or whatever C compiler you are using), so adding new libraries takes a while.

The Lua code is quite simple. The characters — mark comments.  The function `https.request` provides [multiple return values](https://www.lua.org/pil/5.1.html) (a bit like Python and Go). String concatenation is done with the [operator](https://www.lua.org/pil/2.4.html)  .. (instead of + in Python).

The response body from this call is handled with the ‘ltn12’ module – see the [Lua wiki page on Filters, Sources, and Sinks](http://lua-users.org/wiki/FiltersSourcesAndSinks). That enables efficient handling of data that could be returned in multiple “chunks”. As that article explains:

_The table factory creates a sink that stores all obtained data into a table. The data can later be efficiently concatenated into a single string with the table.concat library function._

Our example just concatenates table t and prints it out; you could use a filter to perform more processing.

## Perl

While [Perl](https://www.perl.org/) is famous for its [one liners](https://catonmat.net/introduction-to-perl-one-liners), this is not one of them.  Perl was designed for very fast document search and modification, but is actually capable of so much more.  I once wrote an entire Inventory control suite in Perl.  Go figure.   A n y w a y…

This script makes use of LWP::UserAgent and HTTP::Request and optionally the JSON and Data::Dumper packages depending on how you want to see the output. As with all the other scripts on this page, you should pre-set an environment variable `SPARKPOST_API_KEY`to your generated API key that includes the Recipient Validation function. This script hard codes $recipient = ‘test@gmail.com’ but you can easily add command-line input or consume from a file.

After all the variables are populated, we load an HTTP:Request with GET parameters and send it to the LWP:UserAgent.  The resulting “message” is the result of the email validation test as an array.  You can use JSON and DUMPER to display the result or just pass the array on for additional processing.

## VB.net

Visual Basic is not visual and it is not basic (IMHO), but it is #6 on the [TIOBE language index](https://www.tiobe.com/tiobe-index/visual-basic/) so here we go.

There are [other](https://www.mono-project.com/docs/about-mono/languages/visualbasic/) [ways](https://sourceforge.net/projects/sharpdevelop/) to do this, but the easiest path to success is to use the [Visual Studio](https://learn.microsoft.com/en-us/dotnet/visual-basic/) SDK in a Windows platform. Fire up Visual Studio, start a new project and select Visual Basic, then select console.app.  Be sure to use the VB version not the C# version – it is easy to miss that in the SDK.

At this point you can edit lines manually or copy/paste the code [from here](https://github.com/SparkPost/code-snippets/blob/master/snippets/recipient-validation/VB/validateRecipient.vb) into VS and save a bunch of time. In order to make this code work, you need to add a Windows environment variable.  The easiest way to do this is to open a command prompt and use setx.exe like this: 

In Windows 10, this is applied to your user environment, but is not immediately available in the current command session, so testing it with a “set” will not work, but it will be available to the code. If you build and execute the code included in the repo, you’ll see the validation result. For developers building production webhook consumers that need to validate email addresses at scale, our [Azure Functions webhook consumer guide](/blog/azure-functions) shows how to build serverless solutions that can handle validation workflows efficiently.

## Rust

Rust is a language for systems and web-services programming that is focused on performance, safety and concurrency. As [Wikipedia](<https://en.wikipedia.org/wiki/Rust_(programming_language)>) says, Rust has been the “most loved programming language” in the Stack Overflow Developer Survey since 2016.

The Rust code [in our Github repo](https://github.com/SparkPost/code-snippets/blob/master/snippets/recipient-validation/rust_recipient_validation/src/main.rs) uses the `reqwest` library with `tokio` async, similar to the [example in the Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/web/clients/requests.html). (That’s not a typo, the `reqwest` library name is spelled like that). We’ve included a cargo package manager [configuration file](https://github.com/SparkPost/code-snippets/blob/master/snippets/recipient-validation/rust_recipient_validation/Cargo.toml), so you can build and run with:

This will compile the package into executable code, and run it:

The code uses `std:env`to read the `SPARKPOST_API_KEY` environment variable. A match clause handles the case where the key is undefined. If all is well, a new reqwest::Client is created and an async call issued, followed by an .await? (see the [reqwest documentation](https://rust-lang.github.io/async-book/01_getting_started/04_async_await_primer.html)). Async, rather than the simpler blocking call, seems to be needed to set request headers. Response body text is read with a second `.await?`, as per [this example](https://dtantsur.github.io/rust-openstack/reqwest/struct.Response.html).

## Summary

In this article, we’ve walked through Recipient Validation code examples in many languages. Here’s our request to you.

Let us know if you think we missed your favorite language. We may not have as many examples as [The Fibonacci Project](https://github.com/Random-People/Fibonacci), but we’d love to add some more. Also, if you think our examples can be improved, let us know!

## Q&A

### What is Recipient Validation?

It’s Bird’s API-powered email validation system that classifies addresses as _valid_, _risky_, _neutral_, _undeliverable_, or _typo_, leveraging data from billions of real-world delivery events.

### How does it differ from basic syntax validation?

Traditional validators check only format; Bird’s system evaluates live data such as bounce rates, engagement metrics, and deliverability patterns to make smarter predictions.

### What kind of information does the API return?

Each validation response includes:

- Status classification (valid, risky, etc.)
- Reason code (why an address is flagged)
- Optional “did_you_mean” correction for typos
- Metadata like pricing, country, and status reason fields

### Can I validate emails in bulk?

Yes. You can upload entire lists in the web app or use the API to validate single addresses programmatically as part of your workflow.

### Which programming languages are supported?

Code examples are available for more than a dozen languages — including **Python**, **Node.js**, **PHP**, **Go**, **C#**, **Java**, **Rust**, and **Perl** — covering both modern and legacy systems.

### Where should I store my API key?

Always keep it server-side using an environment variable like `SPARKPOST_API_KEY`. Never embed it in client-side scripts or browser code.

### Can this be integrated into automated workflows?

Absolutely. You can use Bird’s **Flow Builder** or **Azure Functions** to validate addresses in real time before triggering downstream automation, such as confirmation emails or CRM updates.

### Why should businesses care about validation accuracy?

High-quality validation improves sender reputation, prevents bounces, and boosts campaign ROI by ensuring every message reaches a valid inbox.

### What’s next for developers?

Bird invites community contributions for new language examples and improvements to existing ones, helping expand validation accessibility worldwide.