이메일 코드 뜯어 고치기

2024년 4월 19일 금요일

Today I Learned

날짜

2024년 4월 19일 금요일

내용

혁명적으로 힘든 하루였다.

변수 통일

어제 말한대로, 잘못된 변수명을 모두 통일했다. 메일과 관련된 부분이고, 특히 우리 서비스의 가장 핵심적인 부분인 리뷰 작성에 대한 메일발송을 처리하는 부분이기 때문에 기존 기능에 문제가 생겨선 안된다. 그래서 일일이 모두 테스트해서 정상작동하는지 확인해야 했다.

웹훅을 이용해 로컬에서 주문 데이터를 생성하고 메일을 보내기 위한 메시징로그가 어떻게 처리되는지 확인했다. 이 과정에서 리뷰 작성을 유도하는 메일 시스템이 어떻게 진행되는지 더 잘 알 수 있었다. 우선 상품이 주문되어 Lineitem 데이터가 생성되면, 고객이 활성화한 모든 메일의 메시징 로그가 생성된다. First Review Request부터 Logn term Review Request까지. 하지만 First Review 요청을 제외하곤 target_datetime은 값이 비어있어 전송되지 않는다. 이후 배송처리(fullfill)되면 나머지 메시징로그들에도 고객이 설정한 것에 맞게 target_datetime 들이 설정된다. 이후 주기적으로 크론이 작동하며 보내야할 메시지를 보내는 로직이다.

로컬에선 기존 기능들이 잘 작동하는걸 확인했으니, 테스트서버도 확인해야겠다.

메일 전송 로직 통일

우리 서비스에서 보내는 메일은 크게 2가지로 나눌 수 있다. 리뷰 요청을 위한 메일과 서비스 메일이다. 리뷰 요청을 위한 메일은 위에서 작성 한대로 메시징로그를 이용해 처리된다. 서비스 메일이라 함은 우리가 고객에게 보내는 안내 메일 격인데, 코어스크립트가 활성화 되어있지 않을 때, 가입한 이후로 어떤 서비스도 이용하고 있지 않을 때, 서비스를 삭제했을 때 보내는 설문조사 메일 등이 있다. 그냥 리뷰를 위한 메일 이외의 모든 메일이라고 봐도 무방하긴 하다.

두 메일은 각자 별개의 로직을 가지고 있는데, 이를 합치는게 이번 태스크의 목적이었다. 구조를 파악하면서 공수로 보나 코드의 퀄리티로 보나 안합치는게 훨씬 좋다고 판단했다. 두 메일의 차이 중 하나가 “메시징로그” 사용의 유무인데, 이를 바꾸는 건 부적절하다고 생각했기 때문이다. 굳이 메시징로그에 서비스 메일들에 관한 기록까지 추가할 필요가 없으며 괜히 추가하는 과정에서 기존 기능들이 어떻게 고장날지 장담할 수 없다. 추가하는 것 자체도 여러모로 건드릴게 많고..

그렇다고 두 개를 모두 아우를 수 있는 공통 클래스를 만드는 건 의미가 없어보였다. “메일을 전송한다” 라는 것 외에는 아예 다른 과정이 진행되기 때문에 정말 억지로 합쳐진 더러운 코드가 생길 것 같았다. 별개의 코드로 만들고 같은 파일에 넣는 것도 불합리해 보일 정도로 둘은 달랐다.

따라서 진행 방향을 바꿨다. 기존에 1. 템플릿 만드는 함수, 2. 발신자,송신자 등 이메일 설정을 완성하는 함수, 3. 전송하는 함수로 각각 이루어진 것을 하나의 클래스로 관리했다. 그리고 별도로 구현되어 있는 첨부파일을 추가하여 보내는 클래스도 같이 추상 클래스로 만들어주었다.

  • 코드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    
      class SendEmail(ABC):
          def __init__(self, email_config: ServiceEmailConfig):
              self.email_config = email_config
        
          @abstractmethod
          def make_template(self):
              pass
        
          @abstractmethod
          def make_content(self):
              pass
        
          @abstractmethod
          def send_email(self):
              pass
        
      # 일반적인 이메일 전송
      class SendServiceEmail(SendEmail):
          def __init__(self, email_config: ServiceEmailConfig):
              super().__init__(email_config)
        
          def make_template(self): 
        
          def make_content(self):
        
          def send_email(self):
      		    self.make_template()
              self.make_content()
              ses_client.send_email(**self.content)
        
      # 첨부파일이 있는 이메일 전송
      # 수신자는 1명만 가능
      class SendRawEmail(SendEmail):
          def __init__(self, email_config: ServiceEmailConfig , content: str = "",files=[]):
              super().__init__(email_config)
              self.content = content
              self.files = files
        
          def make_template(self):
              msg = MIMEMultipart()
        
          def make_content(self):
              self.msg.attach(MIMEText(self.content, "html"))
        
          def make_attachment(self):
              for file in self.files:
                  part = MIMEApplication(file.file.read(), Name=file.filename)
                  part.add_header("Content-Disposition", "attachment", filename=file.filename)
                  file.file.seek(0)
                  self.msg.attach(part)
        
          def send_email(self):
              self.make_template()
              self.make_content()
              self.make_attachment()
              ses_client.send_raw_email(
                  Source=SERVICE_EMAIL, Destinations=self.email_config.recipient, RawMessage={"Data": self.msg.as_string()}
              )
    

만드는 것도 만드는건데, 기존 이메일을 전송하는 기능들을 다 바꿔서 테스트해보는게 더 힘들다. 하..

회고

반차를 써서 그런가 한 주가 빨리 갔다.