Kotlin email verification

Verify emails in Kotlin with Verifly

Add a live email verification step to any Kotlin app with the Ktor client or an OkHttp coroutine. Verifly is an API-first, pay-as-you-go service — call it from a suspend function, deserialize a typed verdict with kotlinx.serialization, and drop bad mailboxes before they reach your mailer.

Verify one email with the Ktor clientAPI
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable

@Serializable
data class Verdict(val result: String, val is_valid: Boolean)

suspend fun verify(email: String): Verdict {
    val client = HttpClient { install(ContentNegotiation) { json() } }
    return client.use {
        it.get("https://verifly.email/api/v1/verify") {
            header("Authorization", "Bearer vf_YOUR_KEY")
            parameter("email", email)
        }.body()
    }
}

Real-time SMTP mailbox checks

Single, batch, and async bulk verification

Disposable, role account, and catch-all detection

Pay-as-you-go credits with no subscription lock-in

Search fit

Built for Kotlin email verification

Use Verifly when you need a simple API, predictable pricing, and clean JSON results before emails hit your product, CRM, or campaign tool.

Verify signups in a Ktor server route
Screen addresses in an Android app via your own backend
Clean an imported list in a coroutine on Dispatchers.IO
Gate a background job so it only mails deliverable addresses

Coroutine-friendly email verification

Email verification is a network round-trip, which in Kotlin means it belongs inside a suspend function, off the main thread. A regex or a syntactic check only tells you user@dead-domain.com is well-formed; it cannot tell you the mailbox bounces. That answer lives behind an SMTP handshake, and Verifly performs it for you over one HTTPS request you can await from any coroutine.

Two idiomatic clients fit. Ktor's HttpClient is coroutine-native — a get call is already a suspend function, and with the ContentNegotiation plugin and kotlinx.serialization it deserializes the JSON straight into a @Serializable data class. Alternatively, OkHttp with its coroutine-friendly await extension works well, especially on Android where OkHttp is already on the classpath. Either way the response carries a result field of deliverable, undeliverable, or risky plus boolean flags for disposable, role, catch_all, and smtp.

Do not embed the Bearer key in an Android APK — treat it as a server-side secret and proxy verification through your own backend, which is where Ktor server routes shine. Pricing is pay-as-you-go from $2 per 1,000 checks down to $0.60 per 1,000 at volume, and you can self-register for 100 free credits with no card at the autonomous register endpoint.

The same call with OkHttp coroutines

If you already depend on OkHttp, you do not need to add Ktor. OkHttp's Call exposes an await() suspend extension, so a verification becomes a clean suspend function on Dispatchers.IO. Parse the JSON with kotlinx.serialization and branch on the result field. Reuse a single OkHttpClient across your app — it is designed to be shared and pools connections.

Verify with OkHttp + coroutines
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.jsonObject
import okhttp3.*
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

val http = OkHttpClient()

suspend fun Call.await(): Response = suspendCancellableCoroutine { cont ->
    enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) = cont.resumeWithException(e)
        override fun onResponse(call: Call, response: Response) = cont.resume(response)
    })
    cont.invokeOnCancellation { cancel() }
}

suspend fun isDeliverable(email: String): Boolean = withContext(Dispatchers.IO) {
    val req = Request.Builder()
        .url("https://verifly.email/api/v1/verify?email=$email")
        .header("Authorization", "Bearer vf_YOUR_KEY")
        .build()
    http.newCall(req).await().use { resp ->
        val obj = Json.parseToJsonElement(resp.body!!.string()).jsonObject
        obj["result"]?.jsonPrimitive?.content == "deliverable"
    }
}

Batch-verify a list with Ktor

For cleaning many addresses, do not fire the single endpoint in a loop — POST them together to /api/v1/verify/batch. It accepts a JSON body with an emails array and returns a results array in the same order, which deserializes into a list of @Serializable items. Filter the list down to deliverable, non-disposable addresses with a standard Kotlin filter/map.

Batch verification with the Ktor client
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.Serializable

@Serializable
data class Item(val email: String, val result: String, val disposable: Boolean = false)

@Serializable
data class Batch(val results: List<Item>)

@Serializable
data class BatchRequest(val emails: List<String>)

suspend fun cleanList(client: HttpClient, emails: List<String>): List<String> {
    val batch: Batch = client.post("https://verifly.email/api/v1/verify/batch") {
        header("Authorization", "Bearer vf_YOUR_KEY")
        contentType(ContentType.Application.Json)
        setBody(BatchRequest(emails))
    }.body()

    return batch.results
        .filter { it.result == "deliverable" && !it.disposable }
        .map { it.email }
}

FAQ

Frequently asked questions

Ktor client or OkHttp for Verifly in Kotlin?

Both are idiomatic. Ktor's HttpClient is coroutine-native and pairs with kotlinx.serialization for typed deserialization out of the box. OkHttp is a great fit if it is already on your classpath — add a small await() suspend extension and it works cleanly from coroutines. Verifly is a plain REST API, so either suits.

Should I put my key in an Android app?

No. The Bearer key is a server-side secret. If an Android app needs verification, proxy it through your own backend — a Ktor server route, for example — so the key never ships inside the APK where it can be extracted.

How do I deserialize the response?

Declare a @Serializable data class with the fields you need — result: String, is_valid: Boolean, and optional disposable/role/catch_all/smtp with default false — and let kotlinx.serialization map the JSON. With Ktor's ContentNegotiation you just call .body<Verdict>().

How do I batch-verify a list of addresses?

POST them to /api/v1/verify/batch with a JSON emails array and deserialize the results array into a List of items, then filter on the result field. Batching is faster and cheaper than looping the single endpoint in a coroutine.

What fields come back in the JSON?

Each result has result (deliverable, undeliverable, or risky), is_valid, and boolean flags disposable, role, catch_all, and smtp. Give the flags a default of false in your data class so a missing field deserializes safely.

What does Kotlin email verification cost?

Pricing is pay-as-you-go from $2 per 1,000 checks down to $0.60 per 1,000 at volume, with 100 free credits when you self-register. There is no subscription. For AI agents, Verifly also exposes an MCP server at verifly.email/mcp.