legacy/Spring

[Spring] AWS SES를 사용하여 메일을 보내기

heemang.dev 2024. 4. 13. 16:32

진행 중인 프로젝트에서 사용자가 관리자에게 문의 메시지를 보낼 수 있는 기능이 있습니다. 문의를 전송하게 되면 서버에 등록된 관리자 메일로 사용자의 문의 사항이 전송됩니다. 단순하게 사용자 -> 관리자로 메일 전송 기능이 필요로 했고, AWS SES를 사용하여 관리할 수 있도록 하였습니다.

AWS SES

AWS SES(Simple Email Service)를 사용하면 SMTP(Simple Mail Transfer Protocol) 이메일 서버를 온프레미스에 유지하지 않고도 고객과 메일을 전달받을 수 있습니다.

 

즉, 사용자의 이메일 주소와 도메인을 사용하여 이메일을 주고 받기 위해 사용됩니다. 스프링 부트에서 AWS SES를 사용하는 방법에 대해서 알아보겠습니다.

1. 자격 증명 생성

이메일을 관리할 계정을 AWS에 자격 증명을 생성해야 합니다. -> 접속

 

보안 인증 유형으로 이메일 주소를 클릭하고, 이메일을 입력합니다.

 

자격 증명 생성을 클릭하면 AWS로부터 메일이 옵니다. 링크를 클릭하면 메일 인증에 성공하게 됩니다.

 

보안 인증 상태가 다음과 같이 변경됩니다.

인증 전
인증 후

2.  Gradle 설정

스프링 부트에서 ses를 사용할 수 있도록 라이브러리를 추가해줍니다. 

 

build.gradle에 aws-sdk-ses 의존성을 추가합니다.

implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'com.amazonaws:aws-java-sdk-ses:1.12.3'

 

3. application.yml 등록

application.yml에 accesskey와 secretkey를 등록합니다.

aws:
  ses:
    access-key: {AWS access-key}
    secret-key: {AWS secret-key}
    send-mail-to: {관리자 이메일}
  region: {AWS region}

 

4. AWS SES 설정 파일 생성

AWS SES와 관련한 설정 파일을 Bean으로 등록합니다.

  • @Value를 사용하여 application.yml에 등록한 값을 주입 받습니다.
  • amazonSimpleEmailService() : AWS SES 클라이언트를 Bean으로 생성합니다.
    • BasicAwsCredemcials()  : access-key, secret-key를 사용하여 자격 증명을 설정합니다.
    • AWSStaticCredentialsProvider: 정적 자격 증명을 생성합니다. AWS 서비스에 대한 요청을 인증하는 데 필요합니다.
      Application이 시작될 때 한 번만 설정되며, 실행 도중에 변경되지 않습니다.

설정 정보를 마치면 AWS SES 클라이언트를 생성하여 Bean에 등록됩니다.

@Configuration
public class AwsConfig {

    @Value("${aws.ses.access-key}")
    private String accessKey;
    @Value("${aws.ses.secret-key}")
    private String secretKey;
    @Value("${aws.region}")
    private String region;

    @Bean
    public AmazonSimpleEmailService amazonSimpleEmailService() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        AWSStaticCredentialsProvider awsStaticCredentialsProvider = new AWSStaticCredentialsProvider(
            basicAWSCredentials);

        return AmazonSimpleEmailServiceClientBuilder.standard()
            .withCredentials(awsStaticCredentialsProvider)
            .withRegion(region)
            .build();
    }
}

5. 이메일 전송 DTO 생성

서버에서는 사용자의 email, 문의 유형, 문의 제목, 문의 내용을 받게 됩니다. DTO를 사용하여 사용자의 요청이 있을 때 서버에서 받을 수 있도록 합니다.

@Getter
public class SendMailRequestDto {

    private String from; // 보낸 사람 이메일
    private String subject; // 제목
    private String content; // 내용
    private MailStatus mailStatus; // 문의 유형
}

MailStatus는 문의 유형을 의미하고, 다음과 같이 설정합니다.

@Getter
public enum MailStatus {
    SITE("사이트 문의"),
    CREATOR("제작자 문의"),
    ERROR("오류 문의"),
    OTHER("기타");

    private String name;

    MailStatus(String name) {
        this.name = name;
    }
}

 

이메일 전송을 위한 DTO를 생성합니다. 

  • SendMailRequestDto: 사용자가 문의를 작성한 정보를 담는 DTO
  • Destination: 수신자의 이메일 주소를 설정합니다.
  • Message: 이메일의 제목과 본문을 설정합니다.
    • createHtmlBody(): 일정한 형식에 맞춰 메일이 전송될 수 있도록 합니다.
  • createContent(): 주어진 텍스트(String)를 사용하여 AWS SDK의 Content 객체를 생성합니다. 
@Getter
@Component
public class EmailSenderDto {
    @Value("${aws.ses.send-mail-to}")
    private String to;

    public SendEmailRequest toSendRequestDto(SendMailRequestDto request) {
        Destination destination = new Destination()
            .withToAddresses(to);

        Message message = new Message()
            .withSubject(createContent(String.format("[PF - %s] - %s", request.getMailStatus().getName(), request.getSubject())))
            .withBody(new Body()
                .withHtml(createContent(createHtmlBody(request))));

        return new SendEmailRequest()
            .withSource(request.getFrom())
            .withDestination(destination)
            .withMessage(message);
    }

    private Content createContent(String text) {
        return new Content()
            .withCharset("UTF-8")
            .withData(text);
    }

    private String createHtmlBody(SendMailRequestDto request) {
        return "<div style=\" font-family: Arial, sans-serif;\">" +
            "<div style=\"margin-bottom: 20px;\">" +
            "<strong>작성자 이메일:</strong> " + request.getFrom() + "</div>" +
            "<div style=\"margin-bottom: 20px;\">" +
            "<strong>문의 유형:</strong> " + request.getMailStatus().getName() + "</div>" +
            "<div style=\"margin-bottom: 20px;\">" +
            "<strong>문의 제목:</strong> " + request.getSubject() + "</div>" +
            "<div style=\"margin-bottom: 20px;\">" +
            "<strong>문의 내용:</strong> " + request.getContent() + "</div>" +
            "</div>";
    }
}

6. Service 

서비스를 통해 이메일을 전송할 수 있도록 합니다. 

AmazonSimpleMailService에서 제공하는 sendMail()를 사용하여 메일을 전송하게 됩니다. 메일 전송이 정상적으로 처리되면 200 ok를 반환합니다. 이를 사용하여 메일이 전송됐는지 여부를 확인하여 로그를 남깁니다.

@Service
@RequiredArgsConstructor
@Slf4j
public class AmazonMailService {

    private final AmazonSimpleEmailService amazonSimpleEmailService;

    public void sendMail(SendMailRequestDto request) {
        EmailSenderDto dto = new EmailSenderDto();
        SendEmailResult sendResult = amazonSimpleEmailService.sendEmail(
            dto.toSendRequestDto(request));

        // 메일이 정상적으로 전송되면 200 OK를 반환한다.
        if(sendResult.getSdkHttpMetadata().getHttpStatusCode() == 200) {
            log.info("Email sent successfully, Email: {}", request.getFrom());
        } else {
            log.error("Failed to send email, Email: {}", request.getFrom());
        }

    }
}

7. API 호출

API 호출

8. AWS 샌드박스 해제

샌드박스란 도용 및 침해를 방지하기 위해 신규 Amazon SES 계정에 대하여 제한을 걸어둔 것입니다. 샌드박스가 걸려있다면 자격 증명으로 등록되어 있지 않은 계정에는 메일을 보낼 수가 없습니다. 즉, 발신자와 수신자 이메일 계정이 둘다 자격 증명이 되어 있어야 메일을 주고 받을 수 있습니다.

 

관련된 내용은 여기를 참고하셔서 샌드박스를 해제할 수 있습니다.