Key Takeaways
- Kotlin verification uses Ktor's HttpClient with the ContentNegotiation plugin and kotlinx.serialization for typed JSON decoding.
- The verify method is a suspend function, so it composes naturally with coroutines and never blocks a thread while waiting on the API.
- A single shared HttpClient is reused across all requests for connection pooling; create one and close it when done.
- Bulk verification uses structured concurrency: async within a coroutineScope, bounded by a Semaphore so the job stays within the API rate limit.
Kotlin's coroutine model makes it a natural fit for API work like email verification: suspend functions express asynchronous calls without callback nesting, and structured concurrency makes bulk work safe and cancellable. The idiomatic HTTP client is Ktor, which is coroutine-native and pairs with kotlinx.serialization for typed JSON. This guide builds a production verification client against the v2 API, then extends it to bulk verification bounded by a semaphore, on Kotlin 2.x and Ktor 3.x.
The complete reference implementation, including Android and Spring notes, is documented on the verify email with Kotlin integration page.
The v2 Endpoint
Confirm the endpoint against a known address before writing Kotlin. The v2 verify endpoint takes the email as a query parameter and returns a JSON document with all the decision fields.
The status field drives the decision. Passed means the mailbox exists and accepts mail. Failed means the mailbox does not exist or the domain has no MX server. Unknown covers greylisting and transient errors. The boolean flags surface risk categories worth handling separately.
The Serializable Model and Client
Define a data class annotated with @Serializable, using @SerialName to map the snake_case API fields onto idiomatic Kotlin property names. The client wraps a Ktor HttpClient configured with the ContentNegotiation plugin so responses decode automatically.
Three things make this idiomatic Kotlin. The verify function is marked suspend, so it integrates with coroutines and never blocks. The parameter calls handle URL encoding, so the email never needs manual escaping. The ignoreUnknownKeys setting means new fields the API adds later will not break decoding.
Real-Time Verification at Signup
For a single signup check, call verify inside a coroutine and branch on the result with a when expression. Kotlin's when makes the decision logic clear and easy to extend.
The sealed class gives an exhaustive set of outcomes the compiler can check, so adding a new decision branch later forces you to handle it everywhere. This is the kind of correctness Kotlin's type system encourages for decision logic that gates account creation.
Bulk Verification With Structured Concurrency
For bulk work, launch one async per address inside a coroutineScope and bound concurrency with a Semaphore. The withPermit helper acquires and releases a permit around each call, capping in-flight requests so the job stays within the API rate limit.
Structured concurrency guarantees that if any part of the scope fails or is cancelled, all the child coroutines are cancelled too, so there are no leaked requests. The Semaphore caps concurrency at the permit count regardless of list size, and runCatching wraps each call so one failure does not abort the whole batch.
For broader integration patterns across other languages, the email verification integrations hub covers Java, Go, Python, Node.js, and the rest. New developers can grab 100 free email verification credits to test the integration, and the email verification API documentation covers the full response schema.
Frequently Asked Questions
Why use Ktor instead of OkHttp or Retrofit?
Ktor is coroutine-native and multiplatform, so the same client works on the JVM, Android, and Kotlin Multiplatform targets. OkHttp and Retrofit are excellent on the JVM and Android, but Ktor's suspend-first API composes most naturally with structured concurrency for bulk work. Any of the three works for a single signup check.
Do I need kotlinx.serialization, or can I use Jackson or Gson?
kotlinx.serialization is the idiomatic Kotlin choice and integrates directly with Ktor's ContentNegotiation plugin via the @Serializable annotation. Jackson and Gson also work through their own Ktor integrations, but kotlinx.serialization avoids reflection and is compile-time safe, which fits Kotlin best.
How does the Semaphore limit the request rate?
A Semaphore with N permits allows at most N coroutines inside withPermit at once, so no more than N requests are ever in flight. Coroutines that cannot acquire a permit suspend until one frees up, which throttles the job to a sustainable rate without blocking any threads.
Where should I store the API key in a Kotlin app?
Read it from an environment variable or your platform's secret store at startup, never hardcode it. The examples use System.getenv. On Android, keep the key server-side and proxy verification through your backend rather than shipping the key in the app.