소통 하고싶은 개발자

Java Mail API - SMTP를 사용하여 메일 보내기 본문

Java

Java Mail API - SMTP를 사용하여 메일 보내기

OhPro 2022. 7. 3. 10:50
반응형

목차

  • 시작하며
  • Mail API 란?
  • SMTP & Stateful
  • Mail API 사용법
  • 마치며

 


 

시작하며

이번에는 자바 메일 API(javax.mail)에 대해 포스팅하려고 한다. 보통 포스팅은 누군가가 도움을 받기위해 작성한다기보단 내가 나중에 써먹으려고 요약해놓고 까먹으면 보러오기 위해 작성하는데, 이번 주제는 진짜 나중에 까먹을거같아서 포스팅하기로 했다.

 


 

Mail API 란?

자바(Java)에서 메일관련 기능(읽기, 쓰기, 편집하기)을 수행할 수 있게 해주는 표준 확장 패키지다. 

 

메일 API를 사용하게 된 원래 목적이 토이프로젝트에서 이메일 인증 기능을 구현하기 위해서이기 때문에 쓰기 기능만 알아봤고, 그래서 쓰기만 포스팅할것이다.

 


 

SMTP & Stateful

내가 처음 메일 API를 사용하면서 smtp, ssl, tls, 이런 각각의 용어들의 뜻을 잘 몰라서그런지 너무도 애를 먹었다. 그래서 구글링하며 정의에 대해 알아보았다.

 

SMTP : Simple Mail Transfer Protocol 의 약어로 직역하면 간단한 메일 교환 프로토콜

 

SMTP를 사용한 통신 과정을 간단하게 그림으로만 요약하면(전부 알필요는 없음) 아래와 같다.

 

smtp 통신 과정

 

위 과정을 보면 알 수도 있지만 몇 가지 요청과 응답이 연결이 반복되는데, 이는 Stateful한 환경이 아니면 구현이 조금 까다로울 수 있을 것이다.

 

여기서 Stateful이란 것은 곧잘 Stateless와 비교되는데, 간단하게 생각하면 아래와 같이 정의할 수 있다.

  • Stateful : 요청에 대한 응답이 종료되어도 연결은 끊지 않는 통신 방식 -> Connect, Disconnect가 존재.
  • Stateless : 요청에 대한 응답이 종료되면 연결을 끊는 통신 방식 -> 통신이 자동으로 끊기기 때문에 Connect가 존재하지 않음.

 

이런 Stateful한 통신 방식이 네트워크(TCP/IP) 계층에서 일어나면 이러한 통신을 소켓 통신이라고 부르기도 한다. 정확히 소켓 통신이란 네트워크에 데이터를 내보내거나 받기 위한 창구를 하나 만들어두고 그 창구를 통해 통신하는 것을 이야기하는데, 여기서 이 창구가 소켓이고 소켓을 사용한 통신이 소켓 통신이다. 참고로 SMTP도 소켓 통신 중 하나이다.

 

그런데 기본 SMTP는 통신 시 아스키(ASCII)문자를 사용하기 때문에 비교적 쉽게 분석이 가능하다.

 

다시 말하면, SMTP는 암호화되지 않았기 때문에 보안에 취약하다는 것이다.

 

그래서 보안적인 문제를 해결하기 위해 SSL과 TLS를 사용하는 것인데, 간단하게 암호화 해주는 기술쯤으로 여기면 된다. 현재는 거의 대부분 이런 암호화 기술을 사용하고 있고, 보안을 위해서 이 기술을 써줄 필요가 있다.

 

  • SSL : Secure Sockets Layer
  • TLS : Transport Layer Security

 

여기서 잠깐 TMI로 SSL과 TLS의 차이를 보자면 SSL에 버젼이 있는데 SSL 3.0에서 버전이 올라가면서 그냥 이름이 TLS로 바뀐 거라고 한다. 근데 버젼이 바뀌면서 포트 번호나 이것저것 바뀌어서 TLS를 지원하지 않는 곳도 있다고 하던데 현재도 그런지는 모르겠다. (현재는 아니겠지..)

 


 

Mail API 사용법

일단 Mail API를 사용하기 위해 메이븐 의존성을 추가했다.

 

<dependency>
  <groupId>javax.mail</groupId>
  <artifactId>mail</artifactId>
  <version>1.4.7</version>
</dependency>

 

메이븐 의존성 설정 방법은 여기를 참조하기 바란다.

 

public class MailUtil {

    Properties mailProperties;
    Session mailSession;
    String title = "Title is empty";
    String contentText = "Content is empty";
    String sendUserName = "behindsender";
    String sendUserPassword = "************";
    String hostEmail = "*********@gmail.com";

    public MailUtil () {

        mailProperties = new Properties();
        mailProperties.put("mail.transport.protocol", "smtp");
        mailProperties.put("mail.smtp.host", "smtp.gmail.com");
        mailProperties.put("mail.smtp.port", "465");
        mailProperties.put("mail.smtp.auth", "true");
        mailProperties.put("mail.smtp.ssl.enable", "true");
        mailProperties.put("mail.smtp.ssl.trust", "true");
        mailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

        mailSession = Session.getDefaultInstance(mailProperties, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(sendUserName, sendUserPassword);
            }
        });

    }

 

위 코드에서 의미있게 봐야할 것들이 몇 가지 있다.

 

  • Properties (여기를 보면 자세하게 나옴)
    • mail.smtp.host : 어떤 서버로 접속할건지
    • mail.smtp.port : 어떤 포트를 사용해서 접속할건지
    • mail.smtp.auth : true이면 사용자 인증을 시도하는데 기본 값은 false임
    • mail.smtp.ssl.enable : true이면 ssl의 디폴트 포트를 사용해서 ssl을 사용하겠다는 의미
    • mail.smtp.ssl.trust : socket factory가 정의되지 않으면 디폴트로 "MailSSLSocketFactory"를 지정해준다.
    • mail.smtp.socketFactory.class위에서 Stateful을 언급했는데, Stateful에 소켓 통신을 하기때문에 socket factory를 사용해서 소켓을 획득해야하고 그 socket factory를 지정해주는 프로퍼티다.

 

  • Session
  • Authenticator : 위에서 mail.smtp.auth를 true로 해줬기 때문에 등록

 

메일과 관련된 기능을 수행하기 위해선 우선적으로 세션(Session)을 얻어야 하는데, 그러기 위해 Mail API에서 제공하고있는 getDefaultInstance(Properties ,Authenticator) 메서드를 사용한다. 이 때 위에 설정된 파라미터를 읽어가고 일치하는 항목이 있으면 반영되는 것이다.

 

    public void sendMail(String receiver){
        MimeMessage message = new MimeMessage(this.mailSession);

        try {
            Address address = new InternetAddress(hostEmail);
            message.setFrom(address);
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
            message.setSubject(this.title);

            Multipart mp = new MimeMultipart();
            MimeBodyPart mbp = new MimeBodyPart();

            mbp.setText(this.contentText, "UTF-8", "html");
            mp.addBodyPart(mbp);
            message.setContent(mp);

            Transport.send(message);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

위 코드는 실질적으로 메일을 보내는 부분이다. 이전까지는 메일 서버에 접속 및 api 사용 간 보안을 어떤식으로 설정할지 세팅을 했었다. 이 부분은 그 이후에 메일을 보내는 과정이라고 생각하면 된다.

 

  • Address : 메시지 주소를 String으로 갖고있는 클래스다.
  • MimeMessage : Message를 상속받은 클래스로 멀티파트를 지원한다. (텍스트만 보낼거면 SimpleMailMessage를 사용)
    • Message.RecipientType.TO : 누구에게 보낼 것인가 (To)
    • Message.RecipientType.CC : 참조
    • Message.RecipientType.BCC : 숨은참조

 

  • Transport : 누구에게 보낼건지 등에 대한 파라미터를 읽어서 실제로 메일을 보내는 클래스로 send() 메서드만 사용하더라도 connect()부터 close()까지 자동으로 실행되므로 문제는 없다.
  • Multiport, MultiBodyPort : 멀티파트를 이용하기 위함인데, 저렇게 html 형식으로 보내면 태그나 css를 사용할 수 있어서 사용했다.

 

마치며

뭔가 이번 포스팅을 계기로 나중에도 메일 api를 종종 사용할 수 있을 것같아 좋았다. 나는 구글 메일 서버를 사용했는데 네이버같은 메일 서버를 지원하는 다른 곳의 서비스를 이용할 수도 있다. 구글 메일 서버를 이용하기 위해 계정을 만들었다면, 이메일 설정에 IMAP, POP3 설정을 해야한다고 들었는데 안했을때 테스트를 안해봤네....

 

그리고 구글 계정 2단계 인증을 해야 접근이 가능한 건지도 디버깅을 다시 해보고 댓글로 남기겠다.

 

하지만 설정 방식(포트 번호, 도메인)이 달라서 그건 구글링하거나 사이트에 직접 들어가 찾아야 한다.

 

추가적으로 하루에 보낼 수 있는 메일 갯수가 한정되어있고, 한도를 넘기고 싶다면 돈을 내면 된다고 한다^^

 

부족한 부분이나 궁금한 점이 있다면 댓글 부탁드립니다!

감사합니다!!

반응형