Permalink
Browse files

initial version

  • Loading branch information...
1 parent 9e942c4 commit e93f5e69dbd184d1d7ad7d873b5f6f0a9d364bb5 Val Neekman committed Sep 17, 2012
Showing with 259 additions and 0 deletions.
  1. +11 −0 .gitignore
  2. +4 −0 HISTORY
  3. +30 −0 LICENSE
  4. +37 −0 README
  5. +151 −0 emailahoy/__init__.py
  6. +3 −0 pushpi.sh
  7. +23 −0 setup.py
View
@@ -0,0 +1,11 @@
+*.pyc
+*.pyo
+*.db
+*.swp
+*.log
+.DS_Store
+*~
+build
+src
+.svn
+*_db
View
@@ -0,0 +1,4 @@
+
+- Sept 16, 2012 (v 0.1)
+------------------------
+1. Initial release
View
30 LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2012 Val Neekman (val@neekware.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of this project nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
View
37 README
@@ -0,0 +1,37 @@
+=Python EmailAhoy=
+
+A Python email utility that verifies existence of an email address
+
+Author: `Val Neekman <val@neekware.com>`
+
+Introduction
+
+Usage Example:
+--------------
+
+from emailahoy import VerifyEmail, verify_email_address
+
+# to verify and receive the response status use the class
+##############################################
+e = VerifyEmail()
+status = e.verify_email_smtp('test@example.com')
+if e.was_found(status):
+ print >> sys.stderr, "Found:", status
+elif e.not_found(status):
+ print >> sys.stderr, "Not Found:", status
+else:
+ print >> sys.stderr, "Unverifiable:", status
+
+# to just quickly check if an email exist or not use the shorthand function
+##############################################
+if verify_email_address('test@example.com'):
+ print >> sys.stderr, "Found"
+else:
+ print >> sys.stderr, "Not found"
+
+# Note:
+##############################################
+If email is valid, this returns within a second
+If email is not valid or unverifiable, it can take up to 5 seconds
+
+
View
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import DNS
+import smtplib
+import socket
+import re
+
+# only allow the import of our public APIs (UU-SLUG = Uniqure & Unicode Slug)
+__all__ = ['VerifyEmail', 'verify_email_address']
+
+
+class VerifyEmail(object):
+ """ Verify if email exists """
+
+ EMAIL_RE = re.compile('([\w\-\.+]+@\w[\w\-]+\.+[\w\-]+)')
+ default_response = (550, 'Unknown')
+
+ # given a hostname, all mx records will be returned
+ def get_mx_for_hostname(self, hostname):
+ mx = []
+ if self.is_hostname_valid(hostname):
+ try:
+ mx = DNS.mxlookup(hostname)
+ except:
+ pass
+ return mx
+
+ # given a host name, returns True if valid, else False
+ def is_hostname_valid(self, hostname):
+ """ if hostname is valid """
+ try:
+ socket.gethostbyname(hostname)
+ except:
+ return False
+ return True
+
+ # given an email address, returns True if email matches a valid pattern
+ def is_email_valid(self, email):
+ """ if a given email maches the email pattern """
+ return self.EMAIL_RE.search(email)
+
+ # given an email, hostname is returned
+ def get_hostname_from_email(self, email):
+ try:
+ hostname = email.strip().split('@')[1]
+ except:
+ hostname = None
+ return hostname
+
+ # given a hostname, a smtp server connection is returned or None
+ def get_smtp_connection(self, hostname):
+ """ returns server with valid connection if possible """
+ resp = self.default_response
+ connection_success = lambda x: x[0] == 220
+ if self.is_hostname_valid(hostname):
+ server = smtplib.SMTP()
+ try:
+ resp = server.connect(hostname)
+ except:
+ pass
+ if connection_success(resp):
+ return server
+ return None
+
+ # given a response tuple, it returns True if status was success
+ def was_found(self, resp):
+ """ email WAS found """
+ return resp[0] == 250
+
+ # given a response tuple, it returns True if it can tell if email was not found
+ def not_found(self, resp):
+ """ email was NOT found """
+ not_found_words = [
+ "does not exist",
+ "doesn't exist",
+ "rejected",
+ "disabled",
+ "discontinued",
+ "unavailable",
+ "unknown",
+ "invalid",
+ "doesn't handle",
+ ]
+ if resp[0] != 250 and any(a in resp[1].lower() for a in not_found_words):
+ return True
+
+ # given a response tuple, it returns true if it couldn't tell, if email found or not
+ def could_not_verify_status(self, resp):
+ """ email unverifiable """
+ return not (self.was_found(resp) or self.not_found(resp))
+
+ # returns a response tuple indicating the existance of an email address
+ def verify_email_smtp(
+ self,
+ email,
+ from_host='example.com',
+ from_email='verify@example.com'
+ ):
+ """ if an email does exsit """
+
+ cmd_success = lambda x: x[0] == 250
+ found = False
+ resp = self.default_response
+ if self.is_email_valid(email):
+ hostname = self.get_hostname_from_email(email)
+ mx = self.get_mx_for_hostname(hostname)
+ for m in mx:
+ server = self.get_smtp_connection(m[1])
+ if server:
+ try:
+ resp = server.docmd('HELO %s' % from_host)
+ except:
+ continue
+ if cmd_success(resp):
+ try:
+ resp = server.docmd('MAIL FROM: <%s>' % from_email)
+ except:
+ continue
+ if cmd_success(resp):
+ try:
+ resp = server.docmd('RCPT TO: <%s>' % email)
+ except:
+ continue
+ break
+ return resp
+
+# given an email it returns True if it can tell it exist or False
+def verify_email_address(email):
+ e = VerifyEmail()
+ status = e.verify_email_smtp(email)
+ if e.was_found(status):
+ return True
+ return False
+
+# if __name__ == "__main__":
+# # e = VerifyEmail()
+# # status = e.verify_email_smtp(sys.argv[1])
+# # if e.was_found(status):
+# # print >> sys.stderr, "Found:", status
+# # elif e.not_found(status):
+# # print >> sys.stderr, "Not Found:", status
+# # else:
+# # print >> sys.stderr, "Unverifiable:", status
+#
+# if verify_email_address(sys.argv[1]):
+# print >> sys.stderr, "Found"
+# else:
+# print >> sys.stderr, "Not found"
+
+
View
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+
View
@@ -0,0 +1,23 @@
+import os
+from distutils.core import setup
+
+def read(fname):
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(name='python-emailahoy',
+ version='0.1',
+ description = "A Python email utility that verifies existence of an email address",
+ long_description = read('README'),
+ author='Val Neekman',
+ author_email='val@neekware.com',
+ url='http://github.com/un33k/python-emailahoy',
+ packages=['emailahoy'],
+ requires = ['pydns', ],
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: Utilities :: Internet',
+ ],
+ )

0 comments on commit e93f5e6

Please sign in to comment.