본문 바로가기

NCloud

[NCloud] Cloud Insight 로 만드는 메일 반송률 모니터링 서비스

안녕하세요. 
오늘은 NCUC 온라인 밋업에서 발표했던 'Cloud InsightCustom Schema 이용한 메일 발송 모니터링'에 대해 작성해보겠습니다. Cloud Insight의 Custom Schema를 이용하면 디테일하게 원하는대로 성능/운영 지표를 수집할 수 있어 더 심도있는 인사이트를 얻을 수 있습니다.
 

목차

0. 배경 선정 이유

1. Cloud Insight 소개

2. 반송률 (Bounce Rate)?

3. Hands-On

 
 

0. 배경 선정 이유

NCloud의 모니터링 서비스를 주제로 삼기로 한 후, 모니터링을 할 대상에 대해 계속 고민했습니다. 그러던 와중 메일함을 꽉 채운 광고 메일들이 눈에 들어왔습니다. 이거다!
 
대규모 광고 메일을 전송하는 것은 기업에서 흔히 쓰이는 마케팅 수단입니다. NCloud 에서도 대규모 메일 전송을 위한 서비스인 Cloud Outbound Mailer(COM)을 제공하고 있습니다. (안타깝게도 작년 10월 이후 COM의 개인회원의 신규 이용 신청이 제한되었습니다. 기업 계정이나 이미 신청했던 개인 계정만 사용할 수 있음!)
 


 
 
그럼 COM 이용약관을 한번 볼까요. 메일 전송 실패율인 '이메일 반송률' 이 높을 경우 NCloud 측에서 적절한 조치를 취할 수 있다고 나오네요. 여기서 '조치'란 서비스 강제 중지 혹은 계정 정지😱를 의미합니다. 악의적인 스팸 발송자로 판단되면 갑자기 계정이 막힐 수 있겠죠.
또한 향후 마케팅 전략의 측면에서도 발신자가 지속적으로 반송률 측정을 측정하는 것은 발신자 평판(Sender Reputation)에 있어 매우매우 중요합니다!
 
 
 
 

AWS SES 모니터링

 
AWS 에서도 비슷하게 대규모 메일 발송 서비스인 SES (Simple Email Service)가 있는데요. CloudWatch 와 연동하여 반송률과 발송자의 평판지표 (Reputation Rate)를 측정하는 기능이 있습니다. 아직 이 기능이 COM 에는 없기 때문에,
 
NCloud의 모니터링 시스템 Cloud Insight 의 Custom Schema를 사용해 이메일 반송률을 체크하는 기능을 만드는 것이 이번 포스팅의 목표입니다!!!! 실습 내용도 있으니 따라해보세요!
 
 

~오늘의 목표 화면~

 


 
 
 

1. Cloud Insight 소개

이미지 클릭 시 Cloud Insight 소개 페이지로 이동

 
 
Cloud Insight는 클라우드 서비스 및 애플리케이션을 모니터링하고, 성능/운영 지표를 통합 관리하는 서비스입니다. 
 
 

1. 지표 (Metric) 조회 및 시각화 2. Event Rules, Event 관리 3. 사용자 지표, 사용자 대시보드
• Ncloud 서비스와 애플리케이션의 성능/운영지표 통합 관리
• 대시보드 구성으로 운영 현황 한눈에 파악
• Line Chart / Area Chart 등 위젯으로 시각화
• Event Rule 구성으로 Event 발생 시 액션 설정
• Cloud Functions / Webhook과 연동하여 Event 발생 시 자동 해결
🚩 사용자 지표 (Custom Metric)
  : Cloud Insight의 API를 사용하여  
    사용자가 직접 수집하는 성능/운영 지표

• 사용자 대시보드 생성으로 목적별 / 대상별로 사용자화된 위젯과 대시보드 구성

 
 
NCloud 에서 기본적으로 제공하지 않는 지표 외에도 사용자가 Custom Metric을 사용하여 직접 수집할 수 있고, 사용자 대시보드를 만들어서 활용도를 높일 수 있습니다.
 
 

 
이메일 반송률은 NCloud의 기본 제공 메트릭이 아니기 때문에..... 하하 Cloud Insight 의 커스텀 스키마를 사용합니다.
이때 SendData API를 이용해 수집한 데이터를 커스텀 스키마로 전송하는 과정이 포함됩니다. 이후 대시보드나 Event Rule 을 만들 수 있습니다. 
 
 
 
 

2. 반송률 (Bounce Rate)?

수집할 커스텀 지표에 대한 정의를 명확히 하는 것이 먼저 필요하겠죠!
저희가 수집할 이메일 반송률이란 무엇일까요?
 
간단하게 말하면 '이메일 전송에 실패한 비율'인데요.

Bounce, 즉 '반송'은 크게 두가지로 분류됩니다. 
 

소프트 바운스 (Soft Bounce)하드 바운스 (Hard Bounce)
일시적인 전송 실패
ex) 
- 수신자의 메일함 과부화
- 메일의 용량 초과
- 서버 문제
영구적인 전송 실패
ex)
- 존재하지 않는 수신자 이메일 
- 전송을 차단한 수신자 이메일

 
 
 
보통 반송률이라고 하면 하드 바운스를 의미하기 때문에, COM 에서 정의하는 하드 바운스에 대해서 알아보았습니다.

COM 의 Send Block List

 
 
 
COM 가이드에 따르면, 위의 7가지 전송코드(sendResultCode)를 받을 때 하드바운스로 분류하는군요.
전체 이메일 전송 수 中 반송 이메일 건수 (반송률)가 10%가 넘으면 서비스 차단을 검토한답니다.
 
저도 이걸 기준으로 만들어볼게요. 많은 하드바운스 케이스 中 'RECIPIENT_ACCRESS_ERROR' 가 테스트하기 용이해보여서
존재하지 않는 주소로 메일을 보내서, 인위적으로 반송 건수를 발생시킬 것입니다.
 
 
 
 
 

3. Hands-On

실습을 해봅시다!

플로우

 
 
단계
A. COM의 API들을 이용해서 이메일 발송 결과 코드값을 통해 반송 건수를 계산
B. 클라우드 인사이트의 senddata API를 통해서 커스텀 스키마로 전송
C. 그 메트릭 정보로 Event Rule 생성하고 외부로 알람 전송
 
 
 

0. 인증키 발급

가장 먼저 코드 상에서 여러분들의 NCloud 계정에 접근할 수 있도록 인증키를 발급받겠습니다.

 
 
마이페이지 - 계정관리 - 인증키관리 에 들어가서
 '신규 API 인증키 생성'을 누르고
인증키와 시크릿키를 발급받아 주세요
 
 
 

1. [Cloud Insight] Custom Schema 생성

우선 전송할 데이터 형식을 정의하기 위해 Cloud Insight 에서 커스텀 스키마를 생성합니다.
공식 가이드를 참고했습니다. 연두색 글씨만 잘 따라하시면 됩니다.
 
Cloud Insight - Configuration - Custom Schema에 들어가서,
수집대상 설정

 

Product Type : mail_bounce_customSchema
ID Dimension : sendEmail
 
라고 적어서 수집 대상을 설정해줍니다. 생성 버튼 클릭. 
ID Dimension을 발신자 이메일로 두었어요. 
 
 
 
 
 
 

2. [Cloud Insight] 수집할 Custom Metric 설정

이후 Metric 탭에서 ❗수집할 지표 , 메트릭을 추가합니다.
저는 4가지 지표를 수집할 예정이여서, 이 항목들을 추가해주었습니다.
 
 
 

 

request_count (메일 요청 건수)
sent_count (전송 완료 건수)
bounce_count (반송 건수)
bounce_rate (반송률)
위 화면과 같이 추가해주세요.
 

~그럼 커스텀스키마 설정 완료~
 
 
 
 

3. [Cloud Function + COM] 메일 발송

저는 NCloud의 서버리스 서비스인 Cloud Function 에서  COM API를 이용해 메일을 전송해보는 테스트 시나리오를 구상했습니다. (지메일 계정 20개 생성함 하하)

- 20개의 정상(존재하는) 메일 주소로 10분에 한번씩 이메일을 보냄. (반송률 0%)
- 이후 비정상(존재하지 않는) 메일 주소로도 이메일을 보내기 시작함. (반송률 증가)

 

생성된 액션

 
Cloud Functions - Action 들어가서 
Action 생성
트리거 선택 - cron
 
   10분 트리거도 만들어줍시다.
   새로운 트리거 만들기
   cron 정보 설정에서 10분 선택 - 생성
 
이름 : action-bounce-rate-cal
런타임 : python
VPC 정보 : NAT-GW 생성 완료된 Private Subnet 선택 (미리 만들어 두어야 함)
 
코드 내용 : 

더보기
import sys
import os
import hashlib
import hmac
import base64
import requests
import time
import json
import random

mail_request_count = 0
mail_bounce_count = 0
mail_sent_count = 0

def tstamp():
    return str(int(time.time() * 1000))

def make_signature(method, uri):
    timestamp = tstamp()
    access_key = "YourAccessKey"	#발급받은 인증키
    secret_key = "YourSecretKey"
    secret_key = bytes(secret_key, 'UTF-8')
    message = method + " " + uri + "\n" + timestamp + "\n" + access_key
    message = bytes(message, 'UTF-8')
    signingKey = base64.b64encode(hmac.new(secret_key, message, digestmod=hashlib.sha256).digest())
    return signingKey

def send_mail_request():
    global mail_request_count, mail_sent_count, mail_bounce_count
    randint = random.randint(100, 1000)
    endpoint = "https://mail.apigw.ntruss.com"
    uri = "/api/v1/mails"
    address = uri + endpoint
    method = "POST"
    timestamp = tstamp()
    access_key = "YourAccessKey"
    headers = {
        "x-ncp-apigw-timestamp": timestamp,
        "x-ncp-iam-access-key": access_key,
        "x-ncp-apigw-signature-v2": make_signature(method, uri),
    }

    mail_data = {
        "senderAddress": "YourEmailAddress",	#본인의 이메일 주소 
        "recipients": [{"address": f"seeun.aws.ses.test{i}@gmail.com", "type": "R"} for i in range(1, 5)],
        "title": "제목 : SENT MAIL 완료",
        "body": "내용 : 메일이 정상발송 되었습니다.",
    }

    if mail_request_count >= 30 : 
        mail_data = {
            "senderAddress" : "YourEmailAddress",	#본인의 이메일 주소 
            "recipients": [{"address" : "seeun.aws.ses.test1@gmail.com","type":"R"}, {"address" : "seeun.aws.ses.test2@gmail.com","type":"R"}, {"address" : f"seeun.aws.ses.test{randint}@gmail.com","type":"R"}],
            "title": "제목 : SENT MAIL 완료",
            "body": "내용 : 메일이 정상발송 되었습니다. 랜덤 이메일 추가",
        }

    time.sleep(15)
    response = requests.post(endpoint + uri, headers=headers, json=mail_data)

    print(response.status_code)
    print(response.text)
    print(mail_request_count)

    request_id = response.json().get("requestId")
    mail_request_count += response.json().get("count", 0)
    sent_count, bounce_count = get_mail_list_request(request_id)
    print("requestID =", request_id)
    return sent_count, bounce_count

def get_mail_list_request(request_id):
    global mail_bounce_count, mail_request_count, mail_sent_count
    endpoint = "https://mail.apigw.ntruss.com"
    method = "GET"
    uri = f"/api/v1/mails/requests/{request_id}/mails"
    address = endpoint + uri
    timestamp = tstamp()
    access_key = "YourAccessKey"
    headers = {
        "x-ncp-apigw-timestamp": timestamp,
        "x-ncp-iam-access-key": access_key,
        "x-ncp-apigw-signature-v2": make_signature(method, uri),
    }

    time.sleep(15)
    response = requests.get(endpoint + uri, headers=headers)

    print("메일리스트 응답status 코드", response.status_code)
    print("메일리스트 응답 전체 텍스트", response.text)

    mail_list = response.json().get("content", [])
    mail_id_list = [mail.get("mailId") for mail in mail_list]

    for mail_id in mail_id_list:
        get_mail_request(mail_id)

    return mail_sent_count, mail_bounce_count

def get_mail_request(mail_id):
    global mail_request_count, mail_bounce_count, mail_sent_count
    endpoint = "https://mail.apigw.ntruss.com"
    method = "GET"
    uri = f"/api/v1/mails/{mail_id}"
    timestamp = tstamp()
    access_key = "YourAccessKey"
    headers = {
        "x-ncp-apigw-timestamp": timestamp,
        "x-ncp-iam-access-key": access_key,
        "x-ncp-apigw-signature-v2": make_signature(method, uri),
    }

    time.sleep(15)
    
    response = requests.get(endpoint + uri, headers=headers)

    mail_data = response.json()
    print("mail_data 목록 응답", mail_data)
    print(f"Mail ID: {mail_data.get('mailId')}")
    recipients = mail_data.get("recipients", [])
    print("recipients 목록", recipients)
	
    #7가지 하드바운스 코드
    error_codes = ["MAILBOX_ERROR", "MAIL_CONTENTS_ERROR", "NETWORK_ERROR", "RECEIVE_MAIL_SERVICE_ERROR",
                   "RECIPIENT_ADDRESS_ERROR", "SECURITY_AND_POLICY_ERROR", "SMTP_ERROR", 'SEND_BLOCK_ADDRESS']

    mail_bounce_count += sum(1 for recipient in recipients if recipient.get("sendResultCode") in error_codes)
    mail_sent_count += sum(1 for recipient in recipients if recipient.get("sendResultCode") == "MAIL_SENT")

    print("mailsentcount", mail_bounce_count)
    print("total_recipients", mail_request_count)


def main(args):
    sent_count, bounce_count = send_mail_request()
    percentage = round((bounce_count / mail_request_count) * 100, 2) if mail_request_count > 0 else 0
    print("보내진 메일 수", sent_count, "퍼센트", percentage)
    timestamp = tstamp()

    insight_payload = {
        "cw_key": "YourCustomSchemaKey", 
        "data": {
            "sendEmail": "YourEmailAddress",
            "sent_count": sent_count,
            "bounce_count": bounce_count,
            "request_count": mail_request_count,
            "bounce_rate": percentage,
        }
    }

    insight_api_server = "https://cw.apigw.ntruss.com"
    insight_uri = "/cw_collector/real/data"
    insight_access_key = "YourAccessKey"
    insight_secret_key = "YourSecretKey"
    insight_secret_key = bytes(insight_secret_key, 'UTF-8')

    insight_method = "POST"

    insight_http_header = {
        'x-ncp-apigw-signature-v2': make_signature(insight_method, insight_uri),
        'x-ncp-apigw-timestamp': timestamp,
        'x-ncp-iam-access-key': insight_access_key,
    }

    insight_response = requests.post(insight_api_server + insight_uri, headers=insight_http_header, json=insight_payload)

    insight_data = json.loads(insight_response.text)
    return insight_data

0번에서 받은 인증키 정보를 잘 넣어주면 됩니다.
 
이렇게 파이썬 코드를 포함해 정보를 기입하고 액션을 생성합니다.
참고로 코드에서 쓰인 함수에 대한 설명은 다음과 같습니다.

 
각 함수의 응답 값을 던지고 던져 'sendResultCode'를 알아내는게 최종 목표였습니다!! 하하
COM API 가이드를 참고했습니다.
 
~결과~

sendResultCode = "MAIL_SENT"인 메일

 
10분마다 정상 메일 주소로 메일이 발송됩니다.
Cloud Functions - Action - 모니터링 탭에서도 확인할 수 있네요. COM 에서도 리스트업이 가능합니다.

(이후 일정 건수가 넘으면 존재하지 않는 메일 발송이 늘어나, 반송률이 증가하게 됩니다.)
 
 

 

4. [Cloud Insight] Event Rule로 반송률 초과 알람 수신

 
저희가 커스텀 스키마로 모니터링할 지표(반송률)를 생성했죠? 이 지표가 일정 수치를 넘었을 때 사용자에게 알림을 보내줄 수 있도록, Event Rule 을 생성할 것입니다. 반송률이 5%, 10%가 넘었을 때 각각 문자와 이메일을 받아봅시다!!!! 거의 다 왔어요
 
Cloud Insight - Event Rule 생성
감시 상품으로 mail_bounce_customSchema를  선택하고
감시 항목 및 조건 설정 탭에 들어갑니다. 
디멘션은 발신자 이메일로 선택,
저희가 생성한 커스텀 메트릭 중 'bounce_rate'이 감시하고 싶은 메트릭이겠죠? 선택해줍니다.
 
bounce_rate 이 5%는 Warning 레벨, 10%는 Critical 레벨로 설정해줍니다. 10% 넘으면 계정 정지 위기니까... 두번 알려줍시다.
그다음 액션 설정 탭에서 Email과 SMS 로 알림을 받겠다고 선택합니다.
 

Event Rule 설정 완!

 
 
 
~결과~


 
시간이 지나면 반송률이 서서히 올라가 Event Rule 에 의해 발생한 알림이 SMS, Email로 옵니다.
Cloud Insight - Event 탭에서도 발생한 이벤트를 확인할 수 있네요.
 
 
 
 
 

5. [Cloud Insight] 사용자 대시보드

하지만 이렇게 알림으로만 받으면 시간에 따라 지속적으로 팔로우 하기가 힘들겠죠?
Cloud Insight 에는 사용자 대시보드위젯을 추가하여 간단하게 내가 보고 싶은 지표를 시각화하는 기능이 있습니다.
 
 
우선 Cloud Insight - Dashboard 에서 대시보드 생성을 합니다.
위젯 추가 - 데이터 설정 탭에서는
Project Type : mail_bounce_customSchema
Target : 보유 리소스 전체 - 발신자 이메일
Metric : 전체 메트릭 - 커스텀 메트릭 목록
을 선택하여 원하는 위젯을 만듭니다.
 
저는 여러 위젯 종류 중 Time Series 로 2개, Pie Chart 로 1개, Index로 1개의 위젯을 추가하여 대시보드를 구성했습니다.
 

시간 지날수록 바운스 건수와 반송률 증가

 
와 이렇게 대시보드로 시각화하니 여러 지표들을 한눈에 보기 좋네요
 
 


 
 
이렇게 사용자 대시보드 생성까지, COM 이메일 반송률 모니터링 기능을 구성해보았습니다.
 
Cloud Insight 의 커스텀 스키마를 이용하면 이처럼 내가 모니터링하고 싶은 지표를 디테일하게 커스터마이징할 수 있습니다. COM 말고도 다양한 API를 이용해서 나만의 모니터링 기능을 만들어보세요! 😆😆
 
 
 
 
끝으로 1. 이메일 반송률을 낮추기 위한 방법과 2. NCloud 비용 모니터링 포스팅을 참고자료로 추가하며 마무리하겠습니다~!
1. AWS SES BounceRate 관리하기
2. 네이버 클라우드에서 비용 모니터링 설정 및 알림 받기