DEV Community

Maksym Balatsko
Maksym Balatsko

Posted on

πŸ“§ From Syntax to SMTP: The Ultimate Guide to Email Validation in Kotlin


As developers, we've all been there. You build a beautiful sign-up form, and within hours, your database is cluttered with entries like test@test.com, no@no.com, or worse, emails from disposable services like mailinator.com. These bad emails lead to bounced messages, skewed analytics, and a frustrating user experience.

A simple regex can catch basic typos, but it can't tell you if a domain is real, if it's configured to receive email, or if it's a temporary address destined to be abandoned.

What if you had a smarter, more comprehensive tool? Enter emailverifier-kt, a composable, pluggable Kotlin library designed to give you a real signal on whether an email is worth accepting.

What Makes EmailVerifier Different?

This isn't your average validator. It performs a whole suite of checks, giving you a multi-layered defense against bad data.

  • βœ… Syntax Validation: Checks for well-formed email structure (RFC 5322 subset).
  • βœ… Domain Registrability: Uses the Public Suffix List to ensure the domain is actually registrable (goodbye, user@something.invalid).
  • βœ… MX Record Lookup: Queries DNS to see if the domain is configured to receive emails.
  • βœ… Disposable Email Detection: Filters out thousands of known temporary email providers.
  • βœ… Free Provider & Role-Based Checks: Identifies emails from free services (gmail.com) and generic roles (info@, admin@).
  • βœ… Gravatar Check: See if the email has a Gravatar profile, often a good sign of a real user.
  • βœ… SMTP Deliverability Check: (Use with caution!) A deep check that connects to the mail server to see if the mailbox exists.

Best of all, it's built with modern Kotlin in mind, featuring:

  • πŸš€ High Performance: Uses coroutines to run I/O-bound checks concurrently, making it fast and efficient.
  • ✨ Fluent DSL: A clean, readable DSL to configure exactly the checks you need.
  • 🌐 Powerful Offline Mode: Can run using bundled datasets in environments without internet access.
  • πŸ”§ Extreme Configurability: Tweak every aspect, from timeouts to custom data sources.

Getting Started: The Basics

First, add the dependency to your build.gradle.kts:

implementation("io.github.mbalatsko:emailverifier-kt:LATEST_VERSION")
Enter fullscreen mode Exit fullscreen mode

(Check Maven Central for the latest version)

Now, let's see it in action. The library is designed to be reused, so you should create a single instance for your application's lifecycle.

import io.github.mbalatsko.emailverifier.emailVerifier
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    // 1. Create a verifier instance (do this once!)
    // The initialization block downloads the necessary datasets.
    val verifier = emailVerifier {
        // For now, we'll use default settings.
    }

    // 2. Verify an email
    val email = "john.doe@gmail.com"
    val result = verifier.verify(email)

    // 3. Check the result
    if (result.isLikelyDeliverable()) {
        println("'$email' looks like a valid, deliverable email!")
    } else {
        println("Validation failed for '$email'.")
        // The result object gives you granular details
        if (result.disposable is CheckResult.Failed) {
            println("Reason: It's a disposable email address.")
        }
        if (result.mx is CheckResult.Failed) {
            println("Reason: The domain has no MX records.")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The isLikelyDeliverable() function is a great starting point. It aggregates the most critical checks: syntax, domain registrability, MX records, and disposable status.

Use Case 1: In a Spring Boot Application

In a Spring Boot app, you should define the EmailVerifier as a singleton @Bean. This ensures the initial setup cost (downloading data) happens only once at startup.

1. Create a configuration class:

import io.github.mbalatsko.emailverifier.EmailVerifier
import io.github.mbalatsko.emailverifier.emailVerifier
import kotlinx.coroutines.runBlocking
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class EmailVerifierConfig {

    @Bean
    fun emailVerifier(): EmailVerifier {
        // runBlocking is acceptable here as it's part of the bean initialization
        // at application startup.
        return runBlocking {
            emailVerifier {
                // Disable the Gravatar check to speed up validation
                gravatar {
                    enabled = false
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Inject and use it in your service:

import org.springframework.stereotype.Service

@Service
class UserService(private val emailVerifier: EmailVerifier) {

    suspend fun registerUser(email: String, username: String) {
        val validationResult = emailVerifier.verify(email)

        if (!validationResult.isLikelyDeliverable()) {
            throw IllegalArgumentException("Invalid email provided.")
        }

        if (validationResult.roleBasedUsername is CheckResult.Failed) {
            throw IllegalArgumentException("Please use a personal email, not a role-based one.")
        }

        // ... proceed with user registration ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Case 2: On an Android Client

On Android, you need to be mindful of the main thread and data usage. EmailVerifier is perfect for this, thanks to its coroutine support and offline capabilities.

1. Set up your ViewModel:

It's best to use the verifier within a ViewModel and launch verification in viewModelScope. For a snappy user experience and to save data, consider using the offline mode.

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.github.mbalatsko.emailverifier.EmailVerifier
import io.github.mbalatsko.emailverifier.emailVerifier
import kotlinx.coroutines.launch

class RegistrationViewModel : ViewModel() {

    // Lazily initialize the verifier. The first access will trigger
    // the setup, which we'll ensure happens on a background thread.
    private val emailVerifier: EmailVerifier by lazy {
        // Since this is a lazy init, it won't block the main thread on VM creation.
        // The first call to verify() will trigger this block.
        runBlocking {
            emailVerifier {
                // Use the bundled offline data for maximum speed and to save user data.
                // This disables network checks (MX, Gravatar, SMTP).
                allOffline = true
            }
        }
    }

    fun validateEmail(email: String) {
        viewModelScope.launch {
            // This is now super fast because it uses local data!
            val result = emailVerifier.verify(email)

            if (result.isLikelyDeliverable()) {
                // Update UI State: Email is valid
            } else {
                // Update UI State: Email is invalid, maybe show an error message
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

By setting allOffline = true, you get instant feedback on syntax, disposability, and other checks that can use the bundled data, creating a great UX without hitting the network.

Go Forth and Verify!

emailverifier-kt is a powerful, flexible, and modern library that solves a common but tricky problem. By giving you detailed results and extensive configuration, it puts you in control of your application's data quality.

Stop letting bad emails into your system. Give your users instant feedback and ensure the data you collect is reliable.

Check out the project on GitHub to see more advanced configurations and contribute!

Top comments (0)