كيف تتحققون من عناوين البريد الإلكتروني باستخدام Python
التحقق من عنوان بريد إلكتروني في Python سهل في جوانبه البسيطة، وصعب على نحو مفاجئ في الجانب الأهم. يشرح هذا الدليل فحوصات الصياغة واستعلامات DNS التي يمكنكم تشغيلها محليًا، ويوضح بدقة لماذا تفشل محاولات SMTP المباشرة عمليًا، ويبيّن كيف تحصلون على نتيجة على مستوى صندوق البريد عبر REST API.
استخدموا Python لرفض المدخلات المشوهة، ولتخزين نتائج فحوصات DNS/MX مؤقتًا إن شئتم. واستخدموا API للتحقق للحصول على النتيجة على مستوى صندوق البريد، لأن محاولات smtplib المباشرة سهلة التقييد وكثيرًا ما تعيد إجابات غامضة.
ما الذي يعنيه «التحقق» من البريد الإلكتروني فعلًا
التحقق من البريد الإلكتروني هو عملية التأكد من أن العنوان حقيقي وقادر على استقبال البريد قبل أن ترسلوا إليه أي شيء. وتتألف من سلسلة فحوصات، كل منها أصعب من سابقه: هل العنوان مُنسّق بشكل صحيح، وهل يقبل نطاقه البريد أصلًا، وهل صندوق البريد المحدد موجود. في Python يسهل تنفيذ الفحصين الأولين بإتقان. أما الثالث فهو حيث تفشل معظم الحلول المحلية بصمت، وفهم السبب هو أنفع ما في هذا المقال.
الخطة أدناه متعددة الطبقات. شغّلوا الفحوصات الرخيصة والسريعة محليًا لاستبعاد المدخلات المعطوبة بوضوح، ثم استدعوا API للتحقق للحصول على الإجابة على مستوى صندوق البريد حول ما إذا كان العنوان قابلًا للاستخدام. وبذلك ترفضون المدخلات العشوائية فورًا دون أي استدعاء لـ API، وتحصلون على نتيجة أوضح لكل ما يجتاز الفحص.
الخطوة 1: التحقق من الصياغة
يتأكد الفحص الأول من أن العنوان مُصاغ بشكل صحيح. قد يكون من المغري كتابة تعبير نمطي لهذا الغرض، لكن صياغة البريد الإلكتروني أعقد بكثير مما تبدو، والتعابير النمطية المكتوبة يدويًا تكون دائمًا تقريبًا إما صارمة أكثر من اللازم أو متساهلة أكثر من اللازم. والمكتبة المرجعية لهذه المهمة هي email-validator، التي تطبّق المعايير ذات الصلة بشكل سليم وتُستخدم على نطاق واسع في بيئات الإنتاج.
ثبّتوها باستخدام pip:
pip install email-validator
ثم تحققوا من العنوان. تُطلق المكتبة الاستثناء EmailNotValidError لأي عنوان مشوّه، وتعيد كائنًا مُوحّد الصيغة للمدخلات الصالحة:
from email_validator import validate_email, EmailNotValidError
def check_syntax(address):
try:
result = validate_email(address, check_deliverability=False)
return True, result.normalized
except EmailNotValidError as error:
return False, str(error)
ok, value = check_syntax("jane.doe@example.com")
print(ok, value) # True jane.doe@example.com
ok, value = check_syntax("jane.doe@@example")
print(ok, value) # False, with a description of the problem
ضبط check_deliverability=False يُبقي هذه الخطوة فحصًا للصياغة فقط دون أي اتصالات شبكية، فتكون سريعة بما يكفي لتشغيلها مع كل ضغطة مفتاح أو إرسال نموذج. واحرصوا دائمًا على تخزين قيمة normalized التي تعيدها المكتبة بدلًا من مدخلات المستخدم الخام، لأنها توحّد صيغة العنوان لتخزين متسق.
الخطوة 2: فحوصات النطاق وMX
العنوان المُنسّق بشكل صحيح يبقى بلا فائدة إذا كان نطاقه لا يستطيع استقبال البريد. لذا يستعلم الفحص التالي عن سجلات DNS للنطاق ويبحث عن سجلات MX، التي تحدد خوادم البريد المسؤولة عن هذا النطاق. والنطاق الذي لا يملك سجلات MX لا يستطيع قبول البريد، لذا يمكن رفض أي عنوان عليه فورًا.
أما email-validator تستطيع المكتبة القيام بذلك نيابةً عنكم عند تمرير check_deliverability=True، إذ تُجري استعلام MX كجزء من التحقق:
from email_validator import validate_email, EmailNotValidError
def check_domain(address):
try:
result = validate_email(address, check_deliverability=True)
return True, result.domain
except EmailNotValidError as error:
return False, str(error)
إذا أردتم تشغيل استعلام DNS بأنفسكم، ربما لتخزين النتائج مؤقتًا عبر عناوين كثيرة على النطاق نفسه، فاستخدموا مكتبة dnspython مباشرةً:
pip install dnspython
import dns.exception
import dns.resolver
def has_mx_record(domain):
try:
records = dns.resolver.resolve(domain, "MX")
return len(records) > 0
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
return False
except dns.exception.DNSException:
return False # Treat resolver errors as inconclusive.
print(has_mx_record("gmail.com")) # True
print(has_mx_record("no-such-domain-xyz.invalid")) # False
هذا مفيد فعلًا. فهو يحذف العناوين الموجودة على نطاقات انتهت صلاحيتها، أو لم توجد قط، أو ببساطة لا تتعامل مع البريد. لكن لاحظوا ما لم يخبركم به بعد: هل someone@gmail.com صندوق بريد حقيقي أم لا. فـ Gmail يملك سجلات MX، لذا يجتاز كل عنوان Gmail هذا الفحص، صالحًا كان أم لا.
الخطوة 3: فحص صندوق البريد، ولماذا لا تكفي محاولات SMTP المباشرة
يتطلب تأكيد صندوق البريد نفسه التواصل مع خادم البريد المستقبِل. فمن حيث المبدأ، تتصلون عبر SMTP، وتعرّفون بأنفسكم، وتُصدرون أمر RCPT TO مع العنوان المستهدف، ثم تقرأون رمز الاستجابة: الرمز الإيجابي يوحي بأن صندوق البريد موجود، والرفض يوحي بأنه غير موجود. ومكتبة Python المدمجة smtplib تستطيع فعل ذلك، والشيفرة تبدو بسيطة على نحو خادع:
import smtplib
import dns.resolver
def naive_smtp_check(address):
domain = address.split("@")[1]
mx_host = str(dns.resolver.resolve(domain, "MX")[0].exchange)
server = smtplib.SMTP(timeout=10)
server.connect(mx_host)
server.helo("example.com")
server.mail("probe@example.com")
code, message = server.rcpt(address)
server.quit()
# 250 and 251 generally mean the mailbox was accepted.
return code in (250, 251)
هذا ينجح في الدروس التعليمية ويفشل في بيئة الإنتاج. والأسباب تستحق معرفتها بالتفصيل، لأنها تفسّر لماذا لا تعتمد أي أداة تحقق جادة على سكربت كهذا.
سمعة IP والتقييد
يرصد مزوّدو صناديق البريد محاولات التحقق بشكل نشط. شغّلوا بضعة فحوصات RCPT TO من خادم واحد، وسيقيّد مزوّدون مثل Gmail وOutlook عنوان IP هذا أو يحظرونه خلال محاولات قليلة. وبمجرد تقييده، يعيد الخادم استجابات غامضة سواء كان صندوق البريد موجودًا أم لا، فتصبح نتائجكم بلا معنى. ولهذا السبب تحديدًا يتم التحقق الحقيقي من مجموعات كبيرة من عناوين IP المُهيّأة جيدًا والمُدارة بإحكام.
القائمة الرمادية (Greylisting)
تستخدم خوادم كثيرة القائمة الرمادية، فتعيد عمدًا رمز فشل مؤقت 4xx في المرة الأولى التي يتواصل فيها مرسل غير معروف. وخوادم البريد الموثوقة تعيد المحاولة لاحقًا؛ أما مرسلو البريد المزعج فعادةً لا يفعلون. والسكربت الساذج الذي لا يعيد المحاولة يقرأ هذا الفشل المؤقت على أنه رفض، فيصنّف صندوق بريد سليمًا تمامًا على أنه غير صالح. وهذه نتيجة سلبية خاطئة سببها العجلة وحدها.
نطاقات catch-all
نطاق catch-all يقبل البريد لكل عنوان ممكن، لذا فإن RCPT TO يعيد رمزًا إيجابيًا لكل من realperson@domain.com و random-string-9f3a@domain.com على حد سواء. وفحص SMTP المباشر لا يستطيع التمييز بين صندوق بريد حقيقي وآخر غير موجود على هذه النطاقات. ويتطلب كشف catch-all إرسال محاولة ثانية إلى عنوان غير موجود قطعًا ومعرفة ما إذا كان سيُقبل هو الآخر، وهذا كشف catch-all وليس مجرد إجابة بنعم أو لا.
استجابات غامضة من كبار المزوّدين
تعمّد بعض كبار المزوّدين جعل سلوك RCPT TO لديهم غامضًا. فبعضهم يقبل كل عنوان في مرحلة المصافحة ولا يُرجع الارتداد إلا لاحقًا، لذا فإن استجابة SMTP ببساطة لا تحمل المعلومة التي تبحثون عنها.
ولهذا يتعمد القائمون على email-validator استبعاد فحوصات SMTP من المكتبة. وتنص وثائقهم صراحةً على أنه لا فائدة تُرجى من التواصل مع خادم SMTP بأنفسكم، لأن الخوادم بارعة في عدم الكشف عما إذا كان العنوان قابلًا للتسليم، وقد يرتد عنوان بدا أنه يقبل البريد لاحقًا. والخلاصة الصادقة: فحوصات الصياغة وMX يمكنكم إتقانها في شيفرتكم الخاصة، أما فحص صندوق البريد الموثوق فلا.
الخطوة 4: التحقق باستخدام VeriMails API
الحل العملي هو ترك فحص صندوق البريد لخدمة تحقق تعمل من بنية تحتية مصممة لذلك، واستدعاء هذه الخدمة من Python بطلب HTTP واحد. توفر VeriMails REST API تشغّل المسار الكامل: الصياغة، وMX، وDNS، ومصافحة SMTP مباشرة، و كشف catch-all، وكشف العناوين المؤقتة، وكشف العناوين الوظيفية، وتعيد نتيجة واحدة منظمة.
باستخدام مكتبة requests القياسية، يبدو استدعاء التحقق على هذا النحو. ولتفاصيل نقطة النهاية، راجعوا توثيق VeriMails API:
import requests
def verify_email(address, api_key):
response = requests.get(
"https://api.verimails.com/v1/verify",
params={"email": address},
headers={"Authorization": f"Bearer {api_key}"},
timeout=10,
)
response.raise_for_status()
return response.json()
result = verify_email("jane.doe@example.com", api_key="YOUR_API_KEY")
print(result["status"]) # e.g. "deliverable", "undeliverable", "catch_all"
print(result["disposable"]) # True if a throwaway address
print(result["role_based"]) # True for addresses such as info@ or support@
بعد ذلك يمكنكم التصرف بناءً على النتيجة. فقد يقبل نموذج التسجيل العناوين deliverable ، ويرفض العناوين undeliverable و disposable ، ويحدد سياسته الخاصة لعناوين catch_all:
def is_acceptable_for_signup(address, api_key):
result = verify_email(address, api_key)
if result["disposable"]:
return False, "Disposable addresses are not allowed."
if result["status"] == "undeliverable":
return False, "That address does not appear to exist."
return True, "Address verified."
ولأن كل استدعاء يعيد نتيجة واضحة، فهو سريع بما يكفي لتشغيله داخل إرسال النموذج دون إبقاء المستخدم في انتظار. أما لقائمة عناوين موجودة بالفعل في قاعدة بياناتكم، فإن التحقق بالجملة عبر CSV هو الخيار الأنسب، لأن رفع ملف واحد أكفأ بكثير من تكرار استدعاء نقطة النهاية الخاصة بالعنوان الواحد آلاف المرات.
النمط الموصى به لتطبيق Python هو الجمع بين الطبقتين: أبقوا فحص الصياغة المحلي عبر email-validator لرفض المدخلات العشوائية فورًا قبل استهلاك أي رصيد، ثم استدعوا API للحصول على النتيجة المعتمدة لقابلية التسليم لكل ما يجتاز الفحص. فتحصلون على ملاحظات سريعة للأخطاء الواضحة وحكم موثوق للعناوين الحقيقية.
جدول سياسات التطبيق
| النتيجة | سياسة التسجيل | سياسة الاستيراد أو CRM |
|---|---|---|
deliverable | اقبلوا العنوان وخزّنوه بصيغته الموحدة | أبقوه في الشريحة النشطة |
undeliverable | ارفضوه مع رسالة تصحيح واضحة | استبعدوه قبل أي حملة |
catch_all | اقبلوه أو راجعوه بحسب مستوى خطورة الحساب | افصلوه في شريحة مستقلة وأرسلوا بتحفظ |
disposable | ارفضوه في معظم المنتجات | استبعدوه من قوائم التواصل |
role_based | اسمحوا به فقط حين تكون صناديق الوارد المشتركة مقبولة | راجعوه قبل التواصل البارد |
استدعاءات API مقابل مهام القوائم
استخدموا API حيث يهم القرار الفوري: التسجيل، وإتمام الشراء، ونماذج العملاء المحتملين، والدعوات، وتغييرات الحساب. واستخدموا التحقق بالجملة لتصدير CSV، أو تنظيف CRM، أو جمهور حملة يحتاج فيه آلاف العناوين إلى المراجعة نفسها. وينبغي أن يغذّي سيرا العمل كلاهما قواعد الاستبعاد نفسها، حتى لا يُراسَل لاحقًا من أداة أخرى عنوانٌ رُفض في مسار ما.
تجميع كل شيء معًا
التطبيق النظيف يتكون من ثلاث طبقات. أولًا، فحص صياغة عبر email-validator مع check_deliverability=False، يعمل محليًا وفوريًا لرفض المدخلات المشوهة. ثانيًا، فحص MX اختياري عبر dnspython إذا أردتم تصفية النطاقات الميتة قبل استدعاء API، رغم أن API يغطي ذلك أيضًا. ثالثًا، استدعاء VeriMails API للحصول على النتيجة على مستوى صندوق البريد، بما في ذلك كشف catch-all والعناوين المؤقتة والوظيفية الذي لا يمكنكم تكراره بموثوقية بأنفسكم.
ما ينبغي تذكره هو تقسيم الأدوار. فالصياغة وDNS من اختصاصكم فعلًا في Python. أما فحص صندوق البريد المباشر فيعتمد على سمعة IP وسلوك القائمة الرمادية وكشف catch-all، وهي أمور لا يستطيع سكربت واحد إدارتها، ولهذا تستبعد مكتبة التحقق القياسية محاولات SMTP عمدًا. استخدموا الفحوصات المحلية للسرعة، وAPI للتحقق على مستوى صندوق البريد، و صفحة الأسعار لاختيار الباقة التي تناسب الحجم المتوقع لديكم.
الأسئلة الشائعة
مقالات ذات صلة
ابدأوا ببيانات نظيفة
100 رصيد مجاني عند التسجيل. لا حاجة إلى بطاقة ائتمان. طبّقوا النصائح عمليًا اليوم.
ابدأوا مجانًا