كيف أضمن ارسال الرسائل الالكترونية Send email by mailgun؟

السلام عليكم
أحاول استخدام mailgun لإرسال رسائل البريد الالكتروني مثل ( تأكيد البريد - إعادة تعيين كلمة المرور- الخ…)


مصدر الصورة ( https://www.a2hosting.com/blog/wordpress-smtp-server/ )

بعد إنشاء حساب على mailgun

قمت بتهيئة الاعدادات لدي في Django كما هو مذكور في الوثائق

#setting.py

EMAIL_HOST = 'smtp.mailgun.org'
EMAIL_PORT = 587
#مفتاح المستخدم الذي حصلت عليه من من موقع الخدمة
EMAIL_HOST_USER = XXXXXXXXXXXXXXXXXX 
#كلمة سر  المستخدم الذي حصلت عليه من من موقع الخدمة
EMAIL_HOST_PASSWORD = XXXXXXXXXXXXXXXXXX
EMAIL_USE_TLS = True

ثم إنشأت ملف لتجربة ارسال اول رسالة الكترونية

#send_email_test.py

import smtplib
from email.mime.text import MIMEText

msg = MIMEText('Testing some Mailgun awesomness')
msg['Subject'] = "Hello test from send_email_test.py"
msg['From']    = "[email protected]"
msg['To']      = "[email protected]"

s = smtplib.SMTP('smtp.mailgun.org', 587)

# import variable from settings in my project
from django.conf import settings

s.login(settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD)
s.sendmail(msg['From'], msg['To'], msg.as_string())
print('done, has been sent!')
s.quit()

تحققت من البريد الوارد وفعلا الرسالة وصلت خلال ثوانٍ.

الان بعد التجربة حاولت إنشاء حساب جديد في مشروعي وجلست انتظر رسالة التأكيد :joy:


وبعد مرور ثوانٍ قليلة وصلت

كل شيء رائع حتى اللحظة :blush:

لكن بعد التجربة على أكثر من ايميل وانشاء اكثر من حساب احيانا تصل الرسالة وفي بعض الحالات لا تصل !

حاولت الوصول الى لوحة التحكم في mailgun وذهبت للسجلات فوجد الرسائل مكتوب بجانبها Delivered
ونفس الايميلات التي لم تنجح العملية معها جربتها ضمن ملف send_email_test.py وكانت تصل الرسائل بنفس اللحظة
احيانا تصل واحيانا اخرى لا تصل وفي لوحة التحكم دائماً اجدها Delivered هل المشكلة من الخدمة وكيف تعمل من ملف send_email_test.py باستمرار ؟!
اترك الاجابة للمختصين

هذه كانت تجربتي مع mailgun :blush:

3 Likes

تم حل المشكلة من قبل الدعم الخاص بـ mailgun
:blush:

3 Likes

في الواقع هم لم يحلوا المشكلة :smile:

المشكلة الحقيقية في بروتوكول SMTP لإرسال الايميلات

للأسف، بروتوكول SMTP لا يضمن أن تتم العملية بنجاح كل مرة.

لأن الاتصال ممكن ينقطع مع سيرفر SMTP وبهذا لا يمكن أن تعرف إذا نجحت أو لا.

إذا تريد أن تضمن ارسال الايميلات، هذه طريقتين:

1. استعمل الـ API الخاصة بـ mailgun

لأنها مبنية على HTTP وتقدر تحصل على الـ response وتتأكد من نجاح العملية على السيرفر.

تقدر تستعمل مكتبة requests التي تأتي مع بايثون، هذه هي الـ API الخاصة بـ mailgun:

https://documentation.mailgun.com/en/latest/api-sending.html#sending

2. الطريقة الأخرى أن تستعمل message queue (طابور رسائل) للعمليات

بحيث تضمن أن العملية لا تخرج من الطابور إلا لما تتم بشكل سليم باستعمال SMTP.

هذا أيضا يسمح للسيرفر باستيعاب عمليات أكثر بنفس الوقت (لأنها ستبقى بالطابور حتى يقدر ينفذها واحدة واحدة).

الصورة توضح الفرق: على اليسار الطريقة العادية وعلى اليمين الطريقة مع وجود message queue

الطريقة الثانية معقدة نوعاً ما، لكنها تستعمل في أغلب الأنظمة العملاقة لتمرير مثل هكذا عمليات

أخيراً، لا تنسى مراجعة الـ logs على الـ dashboard الخاصة بـ mailgun

1 Like

جميل جداً انا استخدم مكتبة allauth بطبيعة الحال المكتبة مصصمة لارسال البريد للمستخدم في عدة حالات ومنها التسجيل ونسيان كلمة المرور
الان لو اريد ارسال هذه الرسائل عن طريق api هل سأتبع كود المصدر الخاص ب allauth و اقوم بتغيير دالة الارسال بحيث تستخدم json للتواصل مع mailgun-api ؟
هل هذا هو الحل المفترض ؟
ولا حظت في ملف views الخاص بالمكتبة عند ارسال الايميل يتم استخدام ملف adapter.py
وداخل الملف توجد دالة ارسالة وهذا رابط الاسطر البرمجية على github

وهنا في الاسفل تم استدعاء الدالة

لدي استفسارين هل تعتبر طريقة التتبع هذه جيدة وماذا بعد ان عرفت الملف المقصود كيف سأقوم بتعديله ووضعه ضمن رملفات مشروعي وجعل المكتبة مرتبطة به بدلاً من الكود الاساسي

مرة ذكر لي @hichem2h بإنه بنهاية المطاف قام بتخصيص مكتبة خاصة به شبيهة ب allauth
يبدو انني سأضطر بالنهاية لنفس الطريق :joy:

1 Like

استخدمت طريقة api لارسال البريد والجميل هي النتيجة responce العائدة من mailgun ان كانت فشلاً في الارسال او النتيجة تكون 200

قمت بتخصيص ملف adapter.py خاص بي
استخدمت الوراثة class DefaultAccountAdapter من allauth
على هذا الشكل:

#adaptr.py

from allauth.account.adapter import DefaultAccountAdapter

class MyAccountAdapter(DefaultAccountAdapter):

#my custum funtcions here

في مصادر allauth على github الدالة المسؤولة عن ارسال البريد هي send_mail:

تقوم بتمرير الرسالة الى دالة اخرى render_mail:

الدالة وظيفتها معالجة الرسالة الالكتروني و اعادة كائن مستنسخ من EmailMessage جاهز للارسال
ثم عند عودتها تحتوي على خواص من ضمنها الارسال كما في الدالة send_mail:

def send_mail(self, template_prefix, email, context):
    msg = self.render_mail(template_prefix, email, context)
    msg.send() #هنا يتم امر الارسال

لتغيير هذا الاجراء وارسال الرسالة عن طريق api
قمت بإنشاء ملف جديد وظيفته ارسال البريد الالكترني الى mailgun عبر api
بهذا الشكل:

#emailAPI.py
import requests 
from myproject.secret_settings import *
from django.conf import settings


def send_simple_message(subject,body,email):
	print('good')
	return requests.post(
		"https://api.mailgun.net/v3/mg.midgamers.com/messages",
		auth=("api", APIKEY),
		data={"from": settings.DEFAULT_FROM_EMAIL,
			  "to": [email],
			  "subject": subject,
			  "html": body})

تأخذ ٣ عناصر
-subject عنوان
-body محتوى البريد
-email عنوان المرسل له

ثم عدلت القيمة المرجعة من دالة render_mail
بحيث اصبح تعيد كائن Dictionarie يحتوي على subject - bodies - email - from_email

بهذا الشكل:

#adaptr.py

from django.template.loader import render_to_string
from .emailAPI import send_simple_message
from django.contrib import messages



class MyAccountAdapter(DefaultAccountAdapter):

	def render_mail(self, template_prefix, email, context):
		"""
		Renders an e-mail to `email`.  `template_prefix` identifies the
		e-mail that is to be sent, e.g. "account/email/email_confirmation"
		"""
		subject = render_to_string('{0}_subject.txt'.format(template_prefix),
								   context)
		# remove superfluous line breaks
		subject = " ".join(subject.splitlines()).strip()
		subject = self.format_email_subject(subject)

		from_email = self.get_from_email()

		bodies = {}
		for ext in ['html', 'txt']:
			try:
				template_name = '{0}_message.{1}'.format(template_prefix, ext)
				bodies[ext] = render_to_string(template_name,
											   context).strip()
			except TemplateDoesNotExist:
				if ext == 'txt' and not bodies:
					# We need at least one body
					raise
		if 'txt' in bodies:
			msg = {
			"subject": subject,
			"bodies": bodies['html'],
			"from_email": from_email,
			"email": [email]

			}
		else:
			msg = {
				"subject": subject,
				"bodies": bodies['html'],
				"from_email": from_email,
				"email": [email]

			}
		return msg




وقمت بتعديل دالة send_mail لترسل الرسالة عبر api بهذا الشكل:

	def send_mail(self, template_prefix, email, context):
		msg = self.render_mail(template_prefix, email, context)

		response = send_simple_message(msg['subject'], msg['bodies'], msg['email'])

ثم اضفت رسالة تأكيد للمستخدم في حالة كان هناك خطأ في النتيجة العائدة من mailgun api
هكذا:

	def send_mail(self, template_prefix, email, context):
		msg = self.render_mail(template_prefix, email, context)

		response = send_simple_message(msg['subject'], msg['bodies'], msg['email'])

		if not response.status_code == 200:
			messages.add_message(self.request, messages.ERROR, 'تم إنشاء حسابك بنجاح لكن هناك خطأ الرسالة لم تصل تحقق من عنوان بريدك ان كان صحيحا')
		

اخيرا لديك تعديل داخل اعدادات django لتخبره بالتعامل مع ملف adaptr الخاص بك:

#custum my Adapter
ACCOUNT_ADAPTER = 'yourapp.adapter.MyAccountAdapter'

جربت الكود وهو يعمل الان

ربما هناك طرق صحيحة اخرى لكن هذا ما استطعت الوصول له بلغتي الإنجليزية المتعبة :joy:

1 Like

ماشاء الله سامر… عمل كثير و كله ممتاز :heart_eyes:

:joy: حس الاتقان عندنا يستدعي هذا للأسف. لكن في الأخير سيكون عندك مرونة و تحكم أكثر بكودك. و كذلك يمكنك إعادة استعماله بسهولة في مشاريعك القادمة :ok_hand:

بالتوفيق سامر :heart:

1 Like