[Перевод] Создание простого почтового сервиса с помощью Django

Python

Когда мы создаем веб-приложение, часто возникает необходимость отправлять электронные письма. Независимо от того, регистрируется ли пользователь, забывает ли он пароль или подтверждает оплату после размещения заказа, необходимо отправлять различные электронные письма. Таким образом, необходимость отправлять электронную почту на самом деле очень важна, и если вы не создадите хорошо структурированную почтовую службу с самого начала, позже это может привести к беспорядку.

Эта статья не только научит вас определять службу электронной почты, которая может легко переключаться между платформами отправки, но и настраивать фактический контент электронной почты.

используется в этой статьеDjangoдля создания примеров, но я надеюсь, что вы сможете применить основные идеи к другим языкам или фреймворкам, которые вы можете использовать.

Давайте начнем!

базовая отправка почты

Предположим, мы хотим отправить электронное письмо пользователю после того, как он зарегистрировался в нашем веб-приложении. мы можем обратиться кДокументация Джанго, отправьте электронное письмо пользователю, который прошел проверку и был успешно создан. Конкретная реализация выглядит следующим образом:

import logging

from rest_framework.views import APIView
from django.http import JsonResponse
from django.core.mail import send_mail

from users.models import User

logger = logging.getLogger('django')


class RegisterView(APIView):

    def post(self, request):
        # Run validations
        if not request.data:
            return JsonResponse({'errors': 'User data must be provided'}, status=400)
        if User.objects.filter(email=request.data['email']).exists():
            return JsonResponse({'errors': 'Email already in use'}, status=400)
        try:
            # Create new user
            user = User.objects.create_user(email=request.data['email'].lower())
            user.set_password(request.data['password'])
            user.save()
            
            # Send welcome email
            send_mail(
                subject='Welcome!',
                message='Hey there! Welcome to our platform.',
                html_message='<p><strong>Het there!</strong> Welcome to our platform.</p>'
                from_email='from@example.com',
                recipient_list=[user.email],
                fail_silently=False,
            )
            
            return JsonResponse({'status': 'ok'})
        except Exception as e:
            logger.error('Error at %s', 'register view', exc_info=e)
            return JsonResponse({'errors': 'Wrong data provided'}, status=400)

Конечно, как сказано в документации, вы также должны заранее установить некоторые важные элементы конфигурации, такие как EMAIL_HOST и EMAIL_PORT.

очень хороший! Теперь мы отправили приветственное письмо!

Создайте класс почтовой программы

Как я уже говорил, для отправки почты могут потребоваться различные модули нашего приложения, поэтому лучше иметь службу электронной почты или класс почтовой программы для обработки всех почтовых запросов. Это значительно упростит необходимость изменения и повторения электронных писем, поскольку нам больше не нужно каждый раз просматривать весь код.

import logging

from django.conf import settings
from django.core.mail import send_mail

from users.models import User

logger = logging.getLogger('django')


class BaseMailer():
    def __init__(self, to_email, subject, message, html_message):
        self.to_email = to_email
        self.subject = subject
        self.message = message
        self.html_message = html_message

    def send_email(self):
        send_mail(
            subject=self.subject,
            message=self.message,
            html_message=self.html_message,
            from_email='from@example.com',
            recipient_list=[self.to_email],
            fail_silently=False,
        )

Давайте посмотрим, как выглядит слой просмотра зарегистрированного сервиса после этого изменения:

import logging

from rest_framework.views import APIView
from django.http import JsonResponse
from django.core.mail import send_mail

from users.models import User
from users.mailers import BasicMailer

logger = logging.getLogger('django')


class RegisterView(APIView):

    def post(self, request):
        # Run validations
        if not request.data:
            return JsonResponse({'errors': 'User data must be provided'}, status=400)
        if User.objects.filter(email=request.data['email']).exists():
            return JsonResponse({'errors': 'Email already in use'}, status=400)
        try:
            # Create new user
            user = User.objects.create_user(email=request.data['email'].lower())
            user.set_password(request.data['password'])
            user.save()
            
            # Send welcome email
            BasicMailer(to_email=user.email, 
                        subject='Welcome!', 
                        message='Hey there! Welcome to our platform.', 
                        html_message='<p><strong>Het there!</strong> Welcome to our platform.</p>').send_email()
            
            return JsonResponse({'status': 'ok'})
        except Exception as e:
            logger.error('Error at %s', 'register view', exc_info=e)
            return JsonResponse({'errors': 'Wrong data provided'}, status=400)

подкласс почтовой программы

Теперь, когда мы переместили весь «почтовый код» в одно место, пришло время его использовать! На этом этапе вы можете пойти дальше и создать специальные классы почтовых программ и сообщить им, что отправлять каждый раз, когда они вызываются. Теперь давайте создадим класс почтовой программы, который будет вызываться каждый раз, когда пользователь регистрируется, и еще один класс почтовой программы для отправки подтверждения заказа.

import logging

from django.conf import settings
from django.core.mail import send_mail

from users.models import User

logger = logging.getLogger('django')


class BaseMailer():
    def __init__(self, to_email, subject, message, html_message):
        self.to_email = to_email
        self.subject = subject
        self.message = message
        self.html_message = html_message

    def send_email(self):
        send_mail(
            subject=self.subject,
            message=self.message,
            html_message=self.html_message,
            from_email='from@example.com',
            recipient_list=[self.to_email],
            fail_silently=False,
        )
        
        
class RegisterMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email,  
                         subject='Welcome!', 
                         message='Hey there! Welcome to our platform.', 
                         html_message='<p><strong>Het there!</strong> Welcome to our platform.</p>')
        

class NewOrderMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email,  
                         subject='New Order', 
                         message='You have just created a new order', 
                         html_message='<p>You have just created a new order.</p>')

Это показывает, насколько легко интегрировать почтовые службы в различные сценарии. Вам просто нужно создать базовый почтовый класс для реализации, а затем установить конкретное содержимое в подклассе.

Поскольку нам не нужно реализовывать весь код, связанный с электронной почтой, уровень представления, на котором мы регистрируем службу, теперь выглядит более лаконично:

import logging

from rest_framework.views import APIView
from django.http import JsonResponse
from django.core.mail import send_mail

from users.models import User
from users.mailers import RegisterMailer

logger = logging.getLogger('django')


class RegisterView(APIView):

    def post(self, request):
        # Run validations
        if not request.data:
            return JsonResponse({'errors': 'User data must be provided'}, status=400)
        if User.objects.filter(email=request.data['email']).exists():
            return JsonResponse({'errors': 'Email already in use'}, status=400)
        try:
            # Create new user
            user = User.objects.create_user(email=request.data['email'].lower())
            user.set_password(request.data['password'])
            user.save()
            
            # Send welcome email
            RegisterMailer(to_email=user.email).send_email()
            
            return JsonResponse({'status': 'ok'})
        except Exception as e:
            logger.error('Error at %s', 'register view', exc_info=e)
            return JsonResponse({'errors': 'Wrong data provided'}, status=400)

Использование Sendgrid

Предположим, мы должны использовать формальныйбиблиотека питонаПеренесите серверную часть нашего почтового сервисаSendgrid(Платформа для общения с клиентами для транзакционных и маркетинговых электронных писем). Мы больше не сможем использовать Djangosend_emailметод, и нам также пришлось использовать синтаксис новой библиотеки. Ну, а нам все-таки повезло! Поскольку мы собрали весь код, связанный с управлением почтой, в одном месте, мы легко можем внести это изменение 😉

import logging

from django.conf import settings
from sendgrid import SendGridAPIClient, Email, Personalization
from sendgrid.helpers.mail import Mail

from users.models import User

logger = logging.getLogger('django')


class BaseMailer():
    def __init__(self, email, subject, template_id):
        self.mail = Mail()
        self.subject = subject
        self.template_id = template_id

    def create_email(self):
        self.mail.from_email = Email(settings.FROM_EMAIL)
        self.mail.subject = self.subject
        self.mail.template_id = self.template_id
        personalization = Personalization()
        personalization.add_to(Email(self.user.email))
        self.mail.add_personalization(personalization)

    def send_email(self):
        self.create_email()
        try:
            sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
            sg.send(self.mail)
        except Exception as e:
            logger.error('Error at %s', 'mailer', exc_info=e)
            

class RegisterMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email, subject='Welcome!', template_id=1234)

        
class NewOrderMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email, subject='New Order', template_id=5678)

Обратите внимание, что вы должны установить ключ API Sendgrid в файле конфигурации и указать идентификатор используемого шаблона.Sendrid будет напрямую загружать указанный HTML-шаблон электронной почты со своей собственной страницы на основе этого идентификатора.

Очень хороший! Это не сложно, и нам не нужно изменять каждую строку кода, которая отправляет электронные письма.

Теперь пойдем немного дальше.

Настройте содержимое электронной почты на основе информации о домене

Конечно, когда мы отправляем электронные письма, иногда мы также можем использовать некоторую информацию о домене для заполнения шаблона. Например, определенно было бы удобнее указать имя нового пользователя в приветственном письме. Sendgrid позволяет вам определять переменные в шаблонах электронной почты, которые будут заменены фактическими сообщениями, полученными от нас. Итак, теперь давайте добавим эту часть данных!

import logging

from django.conf import settings
from sendgrid import SendGridAPIClient, Email, Personalization
from sendgrid.helpers.mail import Mail

from users.models import User

logger = logging.getLogger('django')


class BaseMailer():
    def __init__(self, email, subject, template_id):
        self.mail = Mail()
        self.user = User.objects.get(email=email)
        self.subject = subject
        self.template_id = template_id
        self.substitutions = {
            'user_name': self.user.first_name,
            'user_surname': self.user.last_name
        }


    def create_email(self):
        self.mail.from_email = Email(settings.FROM_EMAIL)
        self.mail.subject = self.subject
        self.mail.template_id = self.template_id
        personalization = Personalization()
        personalization.add_to(Email(self.user.email))
        personalization.dynamic_template_data = self.substitutions
        self.mail.add_personalization(personalization)

    def send_email(self):
        self.create_email()
        try:
            sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
            sg.send(self.mail)
        except Exception as e:
            logger.error('Error at %s', 'mailer', exc_info=e)
            

class RegisterMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email, subject='Welcome!', template_id=1234)

        
class NewOrderMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email, subject='New Order', template_id=5678)

Единственная проблема, которую я здесь вижу, заключается в том, что альтернативы менее гибкие. Скорее всего, мы передаем данные, к которым у пользователя может не быть доступа в зависимости от контекста запроса. Например, новый номер заказа, ссылка для сброса пароля и т. д. Эти переменные параметры могут быть многочисленными, и передача их в качестве именованных параметров может сделать код беспорядочным. Нам нужен список аргументов переменной длины на основе ключевых слов, который обычно определяется как **kwargs, но здесь мы назовем его **substitutions, чтобы сделать его более выразительным:

import logging

from django.conf import settings
from sendgrid import SendGridAPIClient, Email, Personalization
from sendgrid.helpers.mail import Mail

from users.models import User

logger = logging.getLogger('django')


class BaseMailer():
    def __init__(self, email, subject, template_id, **substitutions):
        self.mail = Mail()
        self.user = User.objects.get(email=email)
        self.subject = subject
        self.template_id = template_id
        self.substitutions = {
            'user_name': self.user.first_name,
            'user_surname': self.user.last_name
        }
        
        for key in substitutions:
            self.substitutions.update({key: substitutions[key]})

    def create_email(self):
        self.mail.from_email = Email(settings.FROM_EMAIL)
        self.mail.subject = self.subject
        self.mail.template_id = self.template_id
        personalization = Personalization()
        personalization.add_to(Email(self.user.email))
        personalization.dynamic_template_data = self.substitutions
        self.mail.add_personalization(personalization)

    def send_email(self):
        self.create_email()
        try:
            sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
            sg.send(self.mail)
        except Exception as e:
            logger.error('Error at %s', 'mailer', exc_info=e)
            

class RegisterMailer(BaseMailer):
    def __init__(self, to_email, **substitutions):
        super().__init__(to_email, subject='Welcome!', template_id=1234, **substitutions)

        
class NewOrderMailer(BaseMailer):
    def __init__(self, to_email):
        super().__init__(to_email, subject='New Order', template_id=5678, **substitutions)

Если вы хотите передать дополнительную информацию классу почтовой программы, вам нужно написать следующий код:

NewOrderMailer(user.email, order_id=instance.id).send_email()
PasswordResetMailer(user.email, key=password_token.key).send_email()

Суммировать

Мы создали гибкий класс почтовой программы, который инкапсулирует весь код, связанный с электронной почтой, в одном месте, что упрощает обслуживание кода, а также принимает переменные параметры контекста для заполнения содержимого электронной почты! Спроектировать сразу все это решение, безусловно, будет сложно, но будет намного проще, если мы будем реализовывать его шаг за шагом, и это принесет много пользы в процессе.Я призываю вас продолжать разработку вложений электронной почты на основе этого дизайна!

Большое спасибо за прочтение этой статьи, надеюсь, она будет полезна для вашего проекта. Также следите за моими предстоящими постами, желаю вам удачи в жизни и счастливого кодирования.

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.