Post

Amazon SNS - 완전 관리형 메시징 서비스

SNS를 활용한 발행-구독 패턴과 멀티 채널 알림 시스템 구축

Amazon SNS란?

Amazon SNS(Simple Notification Service)는 완전 관리형 발행-구독(Pub-Sub) 메시징 서비스임.

하나의 메시지를 여러 구독자에게 동시에 전달할 수 있으며, 이메일, SMS, Lambda, SQS, HTTP 엔드포인트 등 다양한 프로토콜을 지원함.


왜 SNS를 사용해야 하는가?

전통적인 알림 시스템의 문제점

직접 구현 방식:

1
2
3
4
5
애플리케이션 서버
  ├─ SMTP 서버 연결 → 이메일 전송
  ├─ SMS 게이트웨이 호출 → SMS 전송
  ├─ Slack API 호출 → Slack 메시지
  └─ Webhook 호출 → 외부 시스템

문제점:

  1. 복잡한 통합: 각 채널마다 다른 API 구현 필요
  2. 장애 처리: 특정 채널 장애 시 전체 프로세스 영향
  3. 확장 어려움: 새 채널 추가 시 코드 수정 필요
  4. 재시도 로직: 각 채널별로 재시도 구현해야 함
  5. 관리 부담: 인증 정보, 연결 풀, 타임아웃 등 직접 관리

SNS의 해결 방법

발행-구독 패턴:

1
2
3
4
5
6
7
8
애플리케이션
  ↓
SNS Topic (한 번만 발행)
  ├─ Email 구독 → 자동 이메일 전송
  ├─ SMS 구독 → 자동 SMS 전송
  ├─ Lambda 구독 → Slack 전송
  ├─ SQS 구독 → 비동기 처리
  └─ HTTP 구독 → Webhook 호출

장점:

  1. 단일 발행: 한 번 메시지 발행으로 모든 구독자에게 전달
  2. 느슨한 결합: 발행자와 구독자가 독립적
  3. 자동 재시도: 전달 실패 시 자동 재시도
  4. 확장 용이: 구독자 추가/제거 간편
  5. 프로토콜 다양성: 이메일, SMS, HTTP, SQS, Lambda 등

SNS 핵심 개념

1. Topic (토픽)

메시지를 발행하는 논리적 채널. 구독자들이 메시지를 받기 위해 구독하는 대상.

토픽 생성:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { SNSClient, CreateTopicCommand } from '@aws-sdk/client-sns';

const snsClient = new SNSClient({});

const response = await snsClient.send(new CreateTopicCommand({
  Name: 'order-notifications',
  Attributes: {
    DisplayName: '주문 알림',
    DeliveryPolicy: JSON.stringify({
      http: {
        defaultHealthyRetryPolicy: {
          minDelayTarget: 20,
          maxDelayTarget: 20,
          numRetries: 3,
          backoffFunction: 'linear'
        }
      }
    })
  }
}));

const topicArn = response.TopicArn;
// arn:aws:sns:ap-northeast-2:123456789:order-notifications

2. Subscription (구독)

토픽에서 메시지를 수신할 엔드포인트 등록.

지원 프로토콜:

  • Email: 이메일 주소로 전송
  • Email-JSON: JSON 형식으로 이메일 전송
  • SMS: 전화번호로 SMS 전송
  • HTTP/HTTPS: 웹훅으로 POST 요청
  • SQS: SQS 큐로 메시지 전달
  • Lambda: Lambda 함수 트리거
  • Application: 모바일 푸시 알림
  • Firehose: Kinesis Data Firehose로 스트리밍

구독 생성 예시:

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
import { SubscribeCommand } from '@aws-sdk/client-sns';

// 이메일 구독
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'email',
  Endpoint: 'user@example.com'
}));
// 사용자에게 확인 이메일 전송됨 (구독 확인 필요)

// Lambda 구독
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'lambda',
  Endpoint: 'arn:aws:lambda:ap-northeast-2:123456789:function:notification-handler'
}));

// SQS 구독
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'sqs',
  Endpoint: 'arn:aws:sqs:ap-northeast-2:123456789:my-queue',
  Attributes: {
    RawMessageDelivery: 'true'  // SNS 메타데이터 없이 원본 메시지만 전달
  }
}));

3. Message Publishing (메시지 발행)

토픽에 메시지를 발행하면 모든 구독자에게 전달됨.

기본 발행:

1
2
3
4
5
6
7
import { PublishCommand } from '@aws-sdk/client-sns';

await snsClient.send(new PublishCommand({
  TopicArn: topicArn,
  Subject: '주문 완료 알림',
  Message: '주문번호 12345가 성공적으로 처리되었습니다.'
}));

메시지 속성 포함:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
await snsClient.send(new PublishCommand({
  TopicArn: topicArn,
  Message: JSON.stringify({
    orderId: 'order-123',
    customerId: 'user-456',
    totalAmount: 50000,
    status: 'completed'
  }),
  MessageAttributes: {
    orderType: {
      DataType: 'String',
      StringValue: 'express'
    },
    priority: {
      DataType: 'Number',
      StringValue: '1'
    },
    timestamp: {
      DataType: 'String',
      StringValue: new Date().toISOString()
    }
  }
}));

실전 프로젝트 활용 사례

사례 1: 멀티 채널 알림 시스템

아키텍처:

1
2
3
4
5
6
7
주문 완료 이벤트
  ↓
SNS Topic
  ├─ Email Queue → Lambda → SES 이메일 전송
  ├─ SMS Queue → Lambda → SNS SMS 전송
  ├─ Slack Queue → Lambda → Slack Webhook
  └─ Webhook Queue → Lambda → 외부 시스템 호출

메시지 필터링 정책:

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
// 이메일 구독 (email 또는 all 채널만)
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'sqs',
  Endpoint: emailQueueArn,
  Attributes: {
    FilterPolicy: JSON.stringify({
      channel: ['email', 'all']
    }),
    RawMessageDelivery: 'true'
  }
}));

// SMS 구독 (sms 또는 all 채널만)
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'sqs',
  Endpoint: smsQueueArn,
  Attributes: {
    FilterPolicy: JSON.stringify({
      channel: ['sms', 'all']
    }),
    RawMessageDelivery: 'true'
  }
}));

// Slack 구독 (slack 또는 all 채널만)
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'sqs',
  Endpoint: slackQueueArn,
  Attributes: {
    FilterPolicy: JSON.stringify({
      channel: ['slack', 'all']
    }),
    RawMessageDelivery: 'true'
  }
}));

채널별 메시지 발행:

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
// 이메일만 전송
await snsClient.send(new PublishCommand({
  TopicArn: topicArn,
  Message: JSON.stringify({
    title: '주문 완료',
    message: '주문이 성공적으로 처리되었습니다'
  }),
  MessageAttributes: {
    channel: {
      DataType: 'String',
      StringValue: 'email'
    }
  }
}));

// 모든 채널로 전송
await snsClient.send(new PublishCommand({
  TopicArn: topicArn,
  Message: JSON.stringify({
    title: '긴급 알림',
    message: '시스템 점검 예정입니다'
  }),
  MessageAttributes: {
    channel: {
      DataType: 'String',
      StringValue: 'all'
    },
    severity: {
      DataType: 'String',
      StringValue: 'high'
    }
  }
}));

이메일 발송 Lambda:

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
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';

const sesClient = new SESClient({});

export const handler = async (event) => {
  for (const record of event.Records) {
    const message = JSON.parse(record.body);

    // HTML 이메일 템플릿 생성
    const htmlBody = `
      <div style="font-family: Arial, sans-serif;">
        <h2 style="color: #333;">${message.title}</h2>
        <p>${message.message}</p>
        <hr>
        <p style="color: #999; font-size: 12px;">
          발송 시간: ${new Date().toLocaleString('ko-KR')}
        </p>
      </div>
    `;

    await sesClient.send(new SendEmailCommand({
      Source: 'noreply@example.com',
      Destination: {
        ToAddresses: [message.email || 'default@example.com']
      },
      Message: {
        Subject: { Data: message.title },
        Body: {
          Html: { Data: htmlBody },
          Text: { Data: message.message }
        }
      }
    }));

    console.log(`Email sent for: ${message.title}`);
  }
};

SMS 발송 Lambda:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const handler = async (event) => {
  for (const record of event.Records) {
    const message = JSON.parse(record.body);

    // SMS는 160자 제한
    const smsMessage = `[${message.title}] ${message.message}`.substring(0, 160);

    await snsClient.send(new PublishCommand({
      PhoneNumber: message.phoneNumber || '+821012345678',
      Message: smsMessage,
      MessageAttributes: {
        'AWS.SNS.SMS.SMSType': {
          DataType: 'String',
          StringValue: message.severity === 'high' ? 'Transactional' : 'Promotional'
        }
      }
    }));

    console.log(`SMS sent to: ${message.phoneNumber}`);
  }
};

사례 2: Lambda 직접 트리거

CloudWatch Alarm → SNS → Lambda 패턴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
resources:
  Resources:
    HighCPUAlarm:
      Type: AWS::CloudWatch::Alarm
      Properties:
        AlarmName: HighCPUUsage
        MetricName: CPUUtilization
        Namespace: AWS/EC2
        Statistic: Average
        Period: 300
        EvaluationPeriods: 2
        Threshold: 80
        ComparisonOperator: GreaterThanThreshold
        AlarmActions:
          - !Ref AlertTopic

functions:
  alertHandler:
    handler: handler.processAlarm
    events:
      - sns:
          arn: !Ref AlertTopic
          topicName: system-alerts

Lambda에서 알람 처리:

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
export const handler = async (event) => {
  for (const record of event.Records) {
    const snsMessage = JSON.parse(record.Sns.Message);

    // CloudWatch Alarm 메시지 파싱
    const alarmName = snsMessage.AlarmName;
    const newState = snsMessage.NewStateValue;
    const reason = snsMessage.NewStateReason;

    console.log(`Alarm: ${alarmName} is now ${newState}`);
    console.log(`Reason: ${reason}`);

    if (newState === 'ALARM') {
      // Slack으로 알림 전송
      await sendSlackNotification({
        title: `🚨 ${alarmName}`,
        message: reason,
        severity: 'critical'
      });

      // 자동 스케일링 트리거 등
      await triggerAutoScaling();
    }
  }
};

사례 3: Fanout 패턴 (SNS → SQS)

한 이벤트를 여러 큐로 분산:

1
2
3
4
5
6
7
S3 Upload Event
  ↓
SNS Topic
  ├─ Thumbnail Queue → Lambda (썸네일 생성)
  ├─ Metadata Queue → Lambda (메타데이터 추출)
  ├─ Virus Scan Queue → Lambda (바이러스 검사)
  └─ Analytics Queue → Lambda (통계 수집)

Serverless Framework 설정:

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
resources:
  Resources:
    S3EventTopic:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: s3-upload-events

    ThumbnailQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: thumbnail-queue

    ThumbnailQueueSubscription:
      Type: AWS::SNS::Subscription
      Properties:
        TopicArn: !Ref S3EventTopic
        Protocol: sqs
        Endpoint: !GetAtt ThumbnailQueue.Arn
        RawMessageDelivery: true

    # SQS 정책 (SNS가 메시지 전송 허용)
    ThumbnailQueuePolicy:
      Type: AWS::SQS::QueuePolicy
      Properties:
        Queues:
          - !Ref ThumbnailQueue
        PolicyDocument:
          Statement:
            - Effect: Allow
              Principal:
                Service: sns.amazonaws.com
              Action: sqs:SendMessage
              Resource: !GetAtt ThumbnailQueue.Arn
              Condition:
                ArnEquals:
                  aws:SourceArn: !Ref S3EventTopic

functions:
  thumbnailGenerator:
    handler: thumbnail.handler
    events:
      - sqs:
          arn: !GetAtt ThumbnailQueue.Arn
          batchSize: 10

장점:

  • 각 처리 단계가 독립적으로 실패/재시도
  • 새로운 처리 로직 추가 시 구독만 추가하면 됨
  • 각 큐가 독립적으로 스케일링

SMS 전송 (SNS Direct)

국가별 전화번호 형식:

1
2
3
4
5
6
7
8
9
10
11
// 한국
await snsClient.send(new PublishCommand({
  PhoneNumber: '+821012345678',  // +82 (국가 코드) + 10-1234-5678
  Message: '인증 번호: 123456'
}));

// 미국
await snsClient.send(new PublishCommand({
  PhoneNumber: '+12025551234',  // +1 (국가 코드) + 202-555-1234
  Message: 'Verification code: 123456'
}));

SMS 타입:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Transactional (거래형) - 중요한 메시지, 비용 높음, 전달률 높음
await snsClient.send(new PublishCommand({
  PhoneNumber: '+821012345678',
  Message: '계좌 이체가 완료되었습니다',
  MessageAttributes: {
    'AWS.SNS.SMS.SMSType': {
      DataType: 'String',
      StringValue: 'Transactional'
    }
  }
}));

// Promotional (홍보형) - 마케팅 메시지, 비용 낮음
await snsClient.send(new PublishCommand({
  PhoneNumber: '+821012345678',
  Message: '할인 이벤트를 확인하세요!',
  MessageAttributes: {
    'AWS.SNS.SMS.SMSType': {
      DataType: 'String',
      StringValue: 'Promotional'
    }
  }
}));

SMS 설정:

1
2
3
4
5
6
7
8
9
10
import { SetSMSAttributesCommand } from '@aws-sdk/client-sns';

await snsClient.send(new SetSMSAttributesCommand({
  attributes: {
    DefaultSMSType: 'Transactional',  // 기본값 설정
    MonthlySpendLimit: '100',         // 월 지출 한도 ($100)
    DeliveryStatusIAMRole: 'arn:aws:iam::123456789:role/SNSSMSRole',  // 배달 상태 로깅
    DeliveryStatusSuccessSamplingRate: '100'  // 성공 샘플링 비율 100%
  }
}));

메시지 필터링

복잡한 필터 정책:

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
// 구독 생성 시 필터 정책 설정
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'sqs',
  Endpoint: queueArn,
  Attributes: {
    FilterPolicy: JSON.stringify({
      // 숫자 비교
      priority: [{ numeric: ['>=', 1] }],

      // 문자열 일치
      orderType: ['express', 'overnight'],

      // 문자열 접두사
      region: [{ prefix: 'ap-' }],

      // 존재 여부
      customerId: [{ exists: true }],

      // AND 조건 (모든 조건 만족)
      // OR 조건 (배열 내 하나라도 만족)
      severity: ['high', 'critical']
    })
  }
}));

필터 정책 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 고액 주문만 처리
{
  "totalAmount": [{ "numeric": [">=", 1000000] }]
}

// 특정 지역의 긴급 알림만
{
  "region": ["ap-northeast-2", "us-east-1"],
  "severity": ["critical"]
}

// 프리미엄 고객의 모든 주문
{
  "customerTier": ["premium", "vip"]
}

SNS FIFO Topics

순서 보장이 필요한 경우:

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
// FIFO Topic 생성 (.fifo 접미사 필수)
const response = await snsClient.send(new CreateTopicCommand({
  Name: 'order-events.fifo',
  Attributes: {
    FifoTopic: 'true',
    ContentBasedDeduplication: 'true'  // 메시지 내용으로 중복 제거
  }
}));

// FIFO SQS 구독
await snsClient.send(new SubscribeCommand({
  TopicArn: fifoTopicArn,
  Protocol: 'sqs',
  Endpoint: 'arn:aws:sqs:ap-northeast-2:123456789:orders.fifo'
}));

// 메시지 발행 (순서 보장)
await snsClient.send(new PublishCommand({
  TopicArn: fifoTopicArn,
  Message: JSON.stringify({
    orderId: 'order-123',
    status: 'created'
  }),
  MessageGroupId: 'order-123',  // 그룹별 순서 보장
  MessageDeduplicationId: `create-${Date.now()}`  // 중복 제거 ID
}));

await snsClient.send(new PublishCommand({
  TopicArn: fifoTopicArn,
  Message: JSON.stringify({
    orderId: 'order-123',
    status: 'paid'
  }),
  MessageGroupId: 'order-123',  // 같은 그룹 → 순서 보장
  MessageDeduplicationId: `pay-${Date.now()}`
}));

Standard vs FIFO:

특성Standard TopicFIFO Topic
순서보장 안 됨완벽 보장
중복가능정확히 1회 전달
처리량무제한300 msg/s (배치 시 3,000)
구독자모든 프로토콜SQS FIFO만

SNS 모니터링

CloudWatch 메트릭:

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
resources:
  Resources:
    FailedNotificationsAlarm:
      Type: AWS::CloudWatch::Alarm
      Properties:
        AlarmName: SNSFailedNotifications
        MetricName: NumberOfNotificationsFailed
        Namespace: AWS/SNS
        Dimensions:
          - Name: TopicName
            Value: !GetAtt MyTopic.TopicName
        Statistic: Sum
        Period: 300
        EvaluationPeriods: 1
        Threshold: 10
        ComparisonOperator: GreaterThanThreshold
        AlarmActions:
          - !Ref AlertTopic

    SlowDeliveryAlarm:
      Type: AWS::CloudWatch::Alarm
      Properties:
        AlarmName: SNSSlowDelivery
        MetricName: NumberOfNotificationsDelivered
        Namespace: AWS/SNS
        Dimensions:
          - Name: TopicName
            Value: !GetAtt MyTopic.TopicName
        Statistic: Average
        Period: 60
        EvaluationPeriods: 5
        Threshold: 100
        ComparisonOperator: LessThanThreshold

전달 상태 로깅 (HTTP/Lambda):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
await snsClient.send(new CreateTopicCommand({
  Name: 'my-topic',
  Attributes: {
    // Lambda 전달 상태
    LambdaSuccessFeedbackRoleArn: 'arn:aws:iam::123456789:role/SNSFeedbackRole',
    LambdaSuccessFeedbackSampleRate: '100',
    LambdaFailureFeedbackRoleArn: 'arn:aws:iam::123456789:role/SNSFeedbackRole',

    // HTTP 전달 상태
    HTTPSuccessFeedbackRoleArn: 'arn:aws:iam::123456789:role/SNSFeedbackRole',
    HTTPSuccessFeedbackSampleRate: '100',
    HTTPFailureFeedbackRoleArn: 'arn:aws:iam::123456789:role/SNSFeedbackRole'
  }
}));

// CloudWatch Logs에 전달 성공/실패 로그 기록됨

Best Practices

1. DLQ (Dead Letter Queue) 설정

1
2
3
4
5
6
7
8
9
10
11
// SNS 구독에 DLQ 설정
await snsClient.send(new SubscribeCommand({
  TopicArn: topicArn,
  Protocol: 'lambda',
  Endpoint: lambdaArn,
  Attributes: {
    RedrivePolicy: JSON.stringify({
      deadLetterTargetArn: dlqArn
    })
  }
}));

2. 재시도 정책 커스터마이징

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
await snsClient.send(new SetTopicAttributesCommand({
  TopicArn: topicArn,
  AttributeName: 'DeliveryPolicy',
  AttributeValue: JSON.stringify({
    http: {
      defaultHealthyRetryPolicy: {
        minDelayTarget: 20,      // 최소 재시도 간격 (초)
        maxDelayTarget: 600,     // 최대 재시도 간격 (초)
        numRetries: 5,           // 재시도 횟수
        backoffFunction: 'exponential'  // 지수 백오프
      },
      disableSubscriptionOverrides: false
    }
  })
}));

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
// ❌ 큰 데이터를 직접 전송 (256KB 제한)
await snsClient.send(new PublishCommand({
  TopicArn: topicArn,
  Message: JSON.stringify({
    data: largeObject  // 너무 큼
  })
}));

// ✅ S3 참조만 전송
const s3Key = `notifications/${notificationId}.json`;
await s3Client.send(new PutObjectCommand({
  Bucket: 'notification-data',
  Key: s3Key,
  Body: JSON.stringify(largeObject)
}));

await snsClient.send(new PublishCommand({
  TopicArn: topicArn,
  Message: JSON.stringify({
    notificationId,
    s3Bucket: 'notification-data',
    s3Key: s3Key
  })
}));

4. 이메일 구독 자동 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 구독 확인 토큰 자동 처리
export const handler = async (event) => {
  for (const record of event.Records) {
    const message = JSON.parse(record.Sns.Message);

    if (record.Sns.Type === 'SubscriptionConfirmation') {
      // 구독 확인 URL 호출
      const confirmUrl = message.SubscribeURL;
      await axios.get(confirmUrl);
      console.log('Subscription confirmed');
    } else if (record.Sns.Type === 'Notification') {
      // 실제 알림 처리
      await processNotification(message);
    }
  }
};

SNS 비용 최적화

비용 구조:

1
2
3
4
5
6
7
8
9
10
11
12
발행:
- 처음 100만 요청/월: 무료
- 이후: $0.50 per million publishes

이메일/HTTP/Lambda/SQS 전달:
- 무료

SMS:
- 국가별 상이 (한국: ~$0.06/건)

모바일 푸시:
- 무료

최적화 전략:

  1. 필터 정책 활용: 불필요한 메시지 전달 방지
  2. 배치 발행: 여러 메시지를 하나로 묶어 발행
  3. SMS 대신 푸시/이메일: SMS는 비용 높음
  4. SQS Fanout: SNS → SQS → Lambda (재시도 제어 가능)

마치며

Amazon SNS는 확장 가능한 알림 시스템 구축의 핵심입니다.

여러 구독자에게 동시 알림이 필요하거나, 다양한 채널로 메시지를 전송해야 할 때, 이벤트 기반 아키텍처를 구축할 때, Fanout 패턴 구현이 필요할 때, 그리고 시스템 간 느슨한 결합이 필요할 때 SNS를 사용하시면 됩니다.

핵심 패턴으로는 SNS에서 여러 SQS로 분산하는 Fanout, MessageAttributes와 FilterPolicy를 활용한 필터링, 이메일·SMS·Lambda·HTTP를 동시 지원하는 멀티 채널, CloudWatch Alarm에서 SNS를 거쳐 Lambda로 이어지는 이벤트 라우팅이 있습니다.

실전 프로젝트에서는 SNS를 SQS(큐잉), Lambda(처리), SES(이메일)와 조합하여 확장 가능한 알림 시스템을 구축할 수 있습니다.

도움이 되셨길 바랍니다! 😀

This post is licensed under CC BY 4.0 by the author.