[Django] SES로 메일 보내기

caputdraconis·2022년 10월 25일
0
post-thumbnail
/**
	제 벨로그를 방문해주셔서 감사합니다.
    
    Django Admin을 베이스로 작성된 글입니다.
    
    잘못된 정보가 있을 수 있습니다. 댓글이나 caputdraconis@kakao.com으로 오류사항을 전달해주시면 바로 반영하겠습니다.
    
*/

1. 기존의 관리자 대시보드

Django Admin으로 구현된 기존 관리자 대시보드에는, 단체 이메일을 보내는 기능이 존재했습니다. 사용한 라이브러리와 서비스는 아래와 같습니다.

아래 사진과 같이 이메일 템플릿을 사용하여 이메일을 전송하는 기능이였습니다.


하지만, 기존의 메일 시스템의 가장 큰 단점은 직접 이메일 템플릿을 HTML, CSS로 제작하여 이를 붙여넣기 해야한다는 점이였습니다. 이는 관리자 대시보드의 사용자인 에디터 분에게는 아주 곤혹스러운 일이였습니다.

CampaignMonitor에서 이메일 템플릿 제작 -> HTML&CSS 코드를 다운받아 모두 복사 -> 관리자 대시보드에 붙여넣기

CampaignMonitor란? Drag&Drop으로 이메일 템플릿을 제작할 수 있는 서비스입니다.

메일을 보낼 때마다 위 과정을 반복해야 했으니깐요.


2. 지금의 관리자 대시보드

현재의 관리자 대시보드 단체 메일 기능은 기존의 시스템을 모두 버리고 새롭게 처음부터 다시 개발하였습니다. 사용한 라이브러리와 서비스는 아래와 같습니다.

AWS SES란?

AWS에서 제공하는 클라우드 기반 이메일 발송 서비스/플랫폼 입니다. 사용자의 이메일 주소와 도메인을 사용해 SMTP 방식과 API 방식을 사용하여 이메일을 보내고 받을 수 있습니다.

왜 기존의 Mailgun을 버리고 AWS SES로 갈아탔나. 단점은?

AWS SES로 갈아타도록 만든 AWS SES의 장점, 그리고 사용하며 느낀 단점은 아래와 같습니다.

장점

  • 비용이 저렴하다. AWS SES의 가격 정책은 발송 메일 1,000건당 0.1달러입니다. 월 2만통을 보내도 2달러인 수준입니다. Mailgun 기업이 최근에 인수되면서 가격이 올라갔다는 소문이..
  • 메일 신뢰성이 높습니다. 메일 신뢰성을 위하여 지메일을 사용하는 경향이 있었는데, SES는 이용자 관리를 철저하게 해서 메일 신뢰성이 높은 편입니다.

단점

  • 프로덕션에 반영하기까지 시간이 조금 걸린다(1~2일). 서비스에 가입을 하면 SandBox 모드만 사용이 가능합니다. 일 200건 제한, 허용된 이메일 주소로만 발송 가능 등의 제약이 걸리는 모드입니다. 이를 풀기 위해서는 AWS 측에 'SES를 통해 어떤 메일을 보낼것인지, 서비스 소개'를 작성하여 보내야합니다. 물론 영어로!

CKEditor는 왜?

기존 관리자 대시보드의 가장 큰 단점이였던 이메일 템플릿을 해결하기 위해서입니다. 위지위그 리치 텍스트 에디터인 CKEditor를 사용하여 에디터가 직접 사진이나, 표를 넣거나 글을 작성하고 배치할 수 있습니다. 그리고 이 결과값은 HTML로 저장되어 그대로 이메일 템플릿으로 사용할 수 있습니다.




3. 코드

우선 메일 전송만 구현한 코드입니다. 도움이 되었으면 합니다.

Setting

CKEDITOR_UPLOAD_PATH = 'email/images/' # CKEditor에서 이미지를 넣을 때, 이 이미지를 업로드할 경로입니다. 저는 S3의 상대경로로 주었습니다.
CKEDITOR_IMAGE_BACKEND = "pillow"

EMAIL_HOST = os.environ['AWS_SES_API_ENDPOINT'] # AWS-SES -> SMTP Settings로 들어가면 SMTP endpoint에 적혀있는 주소입니다.
EMAIL_HOST_USER = os.environ['AWS_SES_ACCESS_KEY_ID'] # SES Full Access를 가지고 있는 IAM의 Access Key ID
EMAIL_HOST_PASSWORD = os.environ['AWS_SES_SECRET_ACCESS_KEY'] # 위 ID로 적은 IAM 계정의 Secret Access key
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_PORT = 587 # 25, 587, 2587 중 아무거나
EMAIL_USE_TLS = True # TLS는 필수

Model

from django.db import models
from django.utils import timezone
from core.utils import generateUUID
from ckeditor_uploader.fields import RichTextUploadingField
from django.utils.html import strip_tags
from django.core.mail import EmailMultiAlternatives

class OutgoingMail(models.Model):
    class OUTGOING_MAIL_STATUS(models.TextChoices):
        SUCCESS = 'SUCCESS'
        FAIL = 'FAIL'
        TEMPORARY = 'TEMPORARY'


    id = models.TextField(primary_key=True, db_column='id', default=generateUUID, editable=False)
    recipients = models.TextField(
        verbose_name='수신 이메일',
        db_column='recipients',
        blank=False, null=False
    )
    sender = models.CharField(
        verbose_name='발신 이메일',
        db_column='sender',
        blank=False, null=False,
        max_length=50
    )
    subject = models.CharField(
        verbose_name='제목',
        db_column='subject',
        max_length=500,
        blank=False, null=False
    )
    content = RichTextUploadingField(
        verbose_name='내용',
        db_column='content',
        blank=False, null=False
    )
    status = models.TextField(choices=OUTGOING_MAIL_STATUS.choices, verbose_name='상태', default=OUTGOING_MAIL_STATUS.TEMPORARY, editable=False)
    createdat = models.DateTimeField(db_column='createdAt', default=timezone.now, verbose_name='메일 작성일', editable=False)

    def save(self, *args, **kwargs):
        subject, from_email, to = self.subject, self.sender, self.recipients
        plain_message = strip_tags(self.content)
        # recipients to list
        to = to.split(', ')
        # Setting Sender
        msg = EmailMultiAlternatives(subject=subject, body=plain_message, from_email=from_email, to=to)
        msg.attach_alternative(self.content, "text/html")
        msg.content_subtype = 'html'
        msg.mixed_subtype = 'related'
        try:
            msg.send(fail_silently=False)
            self.status = OutgoingMail.OUTGOING_MAIL_STATUS.SUCCESS
        except:
            self.status = OutgoingMail.OUTGOING_MAIL_STATUS.FAIL
        super(OutgoingMail, self).save(*args, **kwargs)

    class Meta:
        managed = True
        db_table = 'outgoing_mail'
        verbose_name='이메일'
        verbose_name_plural = '이메일'

save 메소드를 오버라이드 하여 메일 오브젝트를 저장함과 동시에 메일 전송을 하였습니다. 단체 메일의 경우 수신 메일 주소가 여러개일 수 있기에, 메일 주소를 콤마+공백으로 구분하여 입력하여 이를 split으로 잘라서 리스트로 만들었습니다.

content 는 CKEditor의 RichTextUploadingField를 사용하였습니다.

Admin

from django.contrib import admin
from mail.models import *

@admin.register(OutgoingMail)
class OutgoinMailAdmin(admin.ModelAdmin):
    list_display = ['subject', 'status']



저와 비슷하게 메일 기능을 개발하시면서, 막히는 부분이 있으시다면 본 게시글의 댓글이나 caputdraconis@kakao.com으로 내용 보내주시면 도와드리겠습니다.

0개의 댓글