DEV Community

Cover image for Your Own Email Verification SaaS Server
Saad Alkentar
Saad Alkentar

Posted on

Your Own Email Verification SaaS Server

With an estimated six billion email addresses in the world, it's a huge challenge to know which ones on your contact list are valid. Many are temporary, fraudulent, or simply abandoned. Email verification is a technology designed to solve this by identifying good and bad email addresses without sending an email. It uses a variety of techniques, from simple syntax checks to more complex methods, to assess the validity of an email.

This is critical for many applications:

  • Marketing: Increases deliverability and ROI by ensuring your campaigns reach real people.

  • CRM: Keeps your customer data accurate, leading to better support and engagement.

  • Fraud Prevention: Helps prevent fake sign-ups and transactions on e-commerce sites.

  • Data Hygiene: Maintains a high-quality database for more reliable analytics.

Ultimately, email verification protects your reputation, saves you money, and ensures your communication efforts are not wasted.

The Idea

Well, to make sure an email exists, we should check

  • Check whether the given email address conforms to the general format requirements of valid email addresses.
  • Check whether the domain part of the given email address (the part behind the "@") is known as a disposable and temporary email address domain. These are often used to register dummy users in order to spam or abuse some services.
  • Check whether there is a valid list of servers responsible for delivering emails to the given email address.
  • Check whether the given email address exists by simulating an actual email delivery.

How to do that? Well... luckily, I found a Python library that does that exactly!
It is called py3-validate-email, it covers the 4 points altogether, the only weakness is NOT working with shared hosting servers, so be sure to host your project in a VPS or a dedicated server.

Django project setup

Let's start by creating the Django project and the apps for accounts, admin, and main functionality app

# install Django lib and extensions
pip install Django
pip install django-filter
pip install djangorestframework
pip install djangorestframework_simplejwt
pip install pillow

pip install py3-validate-email

# create the project
django-admin startproject saas
cd saas
python manage.py startapp app_account
python manage.py startapp app_admin
python manage.py startapp app_main
Enter fullscreen mode Exit fullscreen mode

Email verification API

To simplify this step, let's skip using serializers and pass the IP as a GET parameter, in the views file

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from common.utils import *

from validate_email import validate_email

class EmailValidationView(APIView):
    permission_classes = ( )
    renderer_classes = [CustomRenderer, BrowsableAPIRenderer]

    def is_valid_email(self, email):
        is_valid = validate_email(email)
        return is_valid

    def get(self, request, *args, **kwargs):
        email = request.query_params.get('email', None)
        if not email:
            raise APIException("The 'email' query parameter is required.")

        try:
            is_valid = self.is_valid_email(email)
            return Response(is_valid)
        except Exception as e:
            raise APIException(f"Information for email address '{email}' not found.")


Enter fullscreen mode Exit fullscreen mode

app_main/views.py

We are getting the Email from GET parameters, then passing it to py3-validate-email, and responding with its direct response.
Let's assign a URL for the view

from django.urls import path, include
from .views import *

urlpatterns = [
    path('email/', EmailValidationView.as_view()),
]

Enter fullscreen mode Exit fullscreen mode

app_main/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('app_main.urls')),
]
Enter fullscreen mode Exit fullscreen mode

saas/urls.py

let's try it now

GET /api/email/?email=saad.zgm@gmail.com

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status": "success",
    "code": 200,
    "data": true,
    "message": null
}

Enter fullscreen mode Exit fullscreen mode
GET /api/email/?email=saad@gmail.com

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status": "success",
    "code": 200,
    "data": false,
    "message": null
}

Enter fullscreen mode Exit fullscreen mode

Bonus, error handling

Most issues come from smtp. Therefore, it can be useful to debug it using smtp_debug=True flag

validate_email("saad@gmail.com", smtp_debug=True)
19:42:10.715698 connect: to ('gmail-smtp-in.l.google.com', 25) None
19:42:10.890421 reply: b'220 mx.google.com ESMTP a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp\r\n'
19:42:10.890715 reply: retcode (220); Msg: b'mx.google.com ESMTP a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp'
19:42:10.890749 connect: b'mx.google.com ESMTP a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp'
19:42:10.890991 send: 'ehlo saads-MacBook-Pro.local\r\n'
19:42:10.955439 reply: b'250-mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\r\n'
19:42:10.955753 reply: b'250-SIZE 157286400\r\n'
19:42:10.955789 reply: b'250-8BITMIME\r\n'
19:42:10.956789 reply: b'250-STARTTLS\r\n'
19:42:10.956828 reply: b'250-ENHANCEDSTATUSCODES\r\n'
19:42:10.956860 reply: b'250-PIPELINING\r\n'
19:42:10.956884 reply: b'250-CHUNKING\r\n'
19:42:10.956908 reply: b'250 SMTPUTF8\r\n'
19:42:10.956957 reply: retcode (250); Msg: b'mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\nSIZE 157286400\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'
19:42:10.958356 send: 'STARTTLS\r\n'
19:42:11.016002 reply: b'220 2.0.0 Ready to start TLS\r\n'
19:42:11.016104 reply: retcode (220); Msg: b'2.0.0 Ready to start TLS'
19:42:11.224310 send: 'ehlo saads-MacBook-Pro.local\r\n'
19:42:11.289252 reply: b'250-mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\r\n'
19:42:11.289533 reply: b'250-SIZE 157286400\r\n'
19:42:11.289568 reply: b'250-8BITMIME\r\n'
19:42:11.289595 reply: b'250-ENHANCEDSTATUSCODES\r\n'
19:42:11.289620 reply: b'250-PIPELINING\r\n'
19:42:11.289642 reply: b'250-CHUNKING\r\n'
19:42:11.289666 reply: b'250 SMTPUTF8\r\n'
19:42:11.289724 reply: retcode (250); Msg: b'mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\nSIZE 157286400\n8BITMIME\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'
19:42:11.290287 send: 'mail FROM:<saad@gmail.com>\r\n'
19:42:11.355813 reply: b'250 2.1.0 OK a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp\r\n'
19:42:11.356098 reply: retcode (250); Msg: b'2.1.0 OK a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp'
19:42:11.356577 send: 'rcpt TO:<saad@gmail.com>\r\n'
19:42:11.412227 reply: b'550-5.1.1 The email account that you tried to reach does not exist. Please try\r\n'
19:42:11.412510 reply: b"550-5.1.1 double-checking the recipient's email address for typos or\r\n"
19:42:11.412545 reply: b'550-5.1.1 unnecessary spaces. For more information, go to\r\n'
19:42:11.412575 reply: b'550 5.1.1  https://support.google.com/mail/?p=NoSuchUser a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp\r\n'
19:42:11.412633 reply: retcode (550); Msg: b"5.1.1 The email account that you tried to reach does not exist. Please try\n5.1.1 double-checking the recipient's email address for typos or\n5.1.1 unnecessary spaces. For more information, go to\n5.1.1  https://support.google.com/mail/?p=NoSuchUser a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp"
19:42:11.412835 send: 'quit\r\n'
19:42:11.463657 reply: b'221 2.0.0 closing connection a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp\r\n'
19:42:11.463919 reply: retcode (221); Msg: b'2.0.0 closing connection a640c23a62f3a-af921f507c1si1870667666b.448 - gsmtp'
Enter fullscreen mode Exit fullscreen mode
validate_email("saad.zgm@gmail.com", smtp_debug=True)
19:43:07.253112 connect: to ('gmail-smtp-in.l.google.com', 25) None
19:43:07.340865 reply: b'220 mx.google.com ESMTP a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp\r\n'
19:43:07.341149 reply: retcode (220); Msg: b'mx.google.com ESMTP a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp'
19:43:07.341181 connect: b'mx.google.com ESMTP a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp'
19:43:07.341309 send: 'ehlo saads-MacBook-Pro.local\r\n'
19:43:07.392175 reply: b'250-mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\r\n'
19:43:07.392454 reply: b'250-SIZE 157286400\r\n'
19:43:07.392475 reply: b'250-8BITMIME\r\n'
19:43:07.392492 reply: b'250-STARTTLS\r\n'
19:43:07.392521 reply: b'250-ENHANCEDSTATUSCODES\r\n'
19:43:07.392536 reply: b'250-PIPELINING\r\n'
19:43:07.392549 reply: b'250-CHUNKING\r\n'
19:43:07.392562 reply: b'250 SMTPUTF8\r\n'
19:43:07.392590 reply: retcode (250); Msg: b'mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\nSIZE 157286400\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'
19:43:07.392728 send: 'STARTTLS\r\n'
19:43:07.463363 reply: b'220 2.0.0 Ready to start TLS\r\n'
19:43:07.463626 reply: retcode (220); Msg: b'2.0.0 Ready to start TLS'
19:43:07.667088 send: 'ehlo saads-MacBook-Pro.local\r\n'
19:43:07.741871 reply: b'250-mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\r\n'
19:43:07.743250 reply: b'250-SIZE 157286400\r\n'
19:43:07.743800 reply: b'250-8BITMIME\r\n'
19:43:07.743848 reply: b'250-ENHANCEDSTATUSCODES\r\n'
19:43:07.743876 reply: b'250-PIPELINING\r\n'
19:43:07.743900 reply: b'250-CHUNKING\r\n'
19:43:07.743924 reply: b'250 SMTPUTF8\r\n'
19:43:07.743972 reply: retcode (250); Msg: b'mx.google.com at your service, [2a02:3035:b62:b597:7d95:8114:3e04:4156]\nSIZE 157286400\n8BITMIME\nENHANCEDSTATUSCODES\nPIPELINING\nCHUNKING\nSMTPUTF8'
19:43:07.744572 send: 'mail FROM:<saad.zgm@gmail.com>\r\n'
19:43:07.785285 reply: b'250 2.1.0 OK a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp\r\n'
19:43:07.785366 reply: retcode (250); Msg: b'2.1.0 OK a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp'
19:43:07.785530 send: 'rcpt TO:<saad.zgm@gmail.com>\r\n'
19:43:07.957576 reply: b'250 2.1.5 OK a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp\r\n'
19:43:07.957956 reply: retcode (250); Msg: b'2.1.5 OK a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp'
19:43:07.958220 send: 'quit\r\n'
19:43:08.011109 reply: b'221 2.0.0 closing connection a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp\r\n'
19:43:08.011380 reply: retcode (221); Msg: b'2.0.0 closing connection a640c23a62f3a-af91a295112si1970106166b.978 - gsmtp'
Enter fullscreen mode Exit fullscreen mode

That is it for this tutorial. We still need to add the SaaS users/ tokens/ stats management, which we will discuss in another tutorial, so

Stay tuned 😎

Top comments (2)

Collapse
 
danihenrique profile image
Daniel Henrique

That's not that simple. If you start verifying a lot emails, your server will be blacklisted quickly. You have to load balance the verification between different IPs, providers etx

Collapse
 
saad4software profile image
Saad Alkentar

Thanks for your comment Daniel! Yes, you are correct.
I attempted to deploy this method to validate a large contact list on a production server, but due to the high number of invalid emails in the list, my site was blacklisted. The hosting provider then requested that I unblock my site, as it would otherwise shut it down permanently.
That is when I needed the balancing, adding pausing periods for large lists, and more advanced workarounds.
Are you interested in such advanced topics? Or can you explain your approach to avoid blacklisting?