자바 smtp로 메일 보내기

박재한·2022년 7월 26일
0

참고한 글1
참고한 글2

코딩 연습 겸 하여 참고한 글 1 하이퍼링크에 있던 smtp java로 메일 보내기 글을 보고 작성 해 보았다.
intellij idea ce와 maven 프로젝트로 시작하여 작성을 하였다.
maven 사용은 처음이라서 dependency 추가하는 법만 알고 있었다.

필요한 dependency 추가하기

pom.xml에 필요한 요소를 적었다.

처음에 블로그에 있는 그대로 적으려다가
dependency 자동완성에 artifactId에 커서를 보내길래 artifactId를 선 입력 후 groupId를 입력했다.

그리고 자동 완성에 version이 없어 version을 입력하였다.

우상단에 메이븐 새로고침 버튼을 눌러서 Load Maven Changes를 해 주면
groupid, artifactid, version이 잘 맞으면 로드가 잘 되는 것 같다.

로드 전에는 오류로 뜨는데 로드 후에는 오류 없이 뜬다.

maven, gradle 등의 프로젝트 관리 도구를 왜 사용하는지 알게 되었다.
원래는 jar파일을 각자 수집해와서 프로젝트에 import 시키는 과정을 거쳐야 하는데, dependency를 하는 것 만으로도 상대적으로 편리하게 사용할 수 있다는 것이 굉장히 편하게 느껴졌다.

참고한 블로그의 코드를 클래스화

글의 내용과 제목을 담은 클래스 Context, 메일을 보내는 책임을 지고 있는 MailSender 클래스로 나누어 봤다.

Context클래스는 제목인 subject와 글의 내용을 담은 html 문자열이 담겨 있다.


MailSender 클래스는 Context 객체, 보내는 사람, 받는 사람, smtp 계정 아이디, 비밀번호, 상수 등을 담고 있고

메일을 보내기 위한 객체들을 담고 있다.

생성자에서 properties 설정을 많이 하고, context, from, to만 입력받게 하였다.


블로그에서 auth -> send까지 한 번에 나오는 과정을 auth 함수를 만들어 auth 과정만 따로 분리 했다.
public함수이기 때문에 밖에서 호출도 가능하다.
new Auth 과정에서 예외 발생할 수 있기 때문에 throws 처리를 하여 호출하는 상단부에서 처리할 수 있게 하였다.

send는 auth가 필수이기 때문에 send내에서 auth를 호출하여 사용한다.
send 내에서 try-catch로 예외 처리를 시도한다.

try문에 들어가는 모든 코드가 상단 코드이며, Mime객체들을 이용하여 최종적으로 Message 객체의 Context를 설정하는 모습이다.
중간중간에 이 객체가 대충 뭐 하는건지만 이해했고 이 객체가 뭐하는 객체인지 정확히는 짚고 넘어가지 않았다.

catch문에서는 별 다른 처리를 하지 않았다.

클래스 인스턴스를 생성 후 필요한 데이터를 넣어 send 메소드를 호출

main이 담긴 코드에서는 생성한 클래스 인스턴스를 생성하여 send 테스트를 하였다.

보안성은 1도 없지만 메인코드 내에서 비밀번호를 지면에 남기기 싫어 base64인코딩 된 값을 넣었다.
auth 메소드에서 string으로 인자를 넘겨받아 base64 디코딩 과정을 거친 후 auth객체에 넘겨 auth 과정을 진행한다.

코드

App.java

import mail.*;

import java.util.Base64;

public class App {
    public static void main(String[] args) {
        Context context = new Context("메일 발송 테스트");
        context.addLine("메일의 첫 번째 내용입니다.")
                .addLine("메일의 두 번째 내용입니다")
                .addLine("메일의 세 번째 내용입니다.");

        MailSender sender = new MailSender(context, "any991020@naver.com", "any991020@naver.com");

        sender.send("any991020", "YXNkZmFzZGYK");
    }
}

MailSender.java

package mail;

import javax.activation.CommandMap;
import javax.activation.MailcapCommandMap;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.util.Base64;
import java.util.Date;
import java.util.Properties;

public class MailSender {
    final String ENCODING = "UTF-8";
    final String PORT = "465";
    final String SMTPHOST = "smtp.naver.com";

    Context context;
    String from;
    String to;

    String account;
    String password;

    Properties properties;
    Authenticator auth;
    Session session;
    Message message;
    Multipart multipart;
    MimeBodyPart bodyPart;
    MailcapCommandMap commandMap;

    public MailSender(Context context, String from, String to) {
        this.context = context;
        this.from = from;
        this.to = to;

        properties = new Properties();

        properties.put("mail.transport.protocol", "smtp");
        properties.put("mail.smtp.host", SMTPHOST);
        properties.put("mail.smtp.port", PORT);
        properties.put("mail.smtp.auth", true);
        properties.put("mail.smtp.ssl.enable", true);
        properties.put("mail.smtp.ssl.trust", SMTPHOST);
        properties.put("mail.smtp.starttls.required", "true");
        properties.put("mail.smtp.ssl.protocols", "TLSv1.2");

        properties.put("mail.smtp.quit-wait", "false");
        properties.put("mail.smtp.socketFactory.port", PORT);
        properties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        properties.put("mail.smtp.socketFactory.fallback", "false");
    }

    public void auth(String account, String password) throws Exception {
        this.account = account;

        byte[] decoded = Base64.getDecoder()
                .decode(password.getBytes());

        String decodedPassword = new String(decoded);
        String authPassword = decodedPassword.substring(0, decodedPassword.length() - 1);

        this.auth = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(account, authPassword);
            }
        };
    }

    public void send(String account, String password) {
        assert account == null || password == null || auth == null;

        try {
            auth(account, password);

            session = Session.getInstance(properties, auth);

            message = new MimeMessage(session);
            message.setFrom(new InternetAddress(from, to));
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false));
            message.setSubject(context.getSubject());
            message.setSentDate(new Date());

            multipart = new MimeMultipart();
            bodyPart = new MimeBodyPart();

            bodyPart.setText(context.getHtml(), ENCODING, "html");

            multipart.addBodyPart(bodyPart);
            message.setContent(multipart);

            commandMap = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
            commandMap.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
            commandMap.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
            commandMap.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
            commandMap.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
            commandMap.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
            CommandMap.setDefaultCommandMap(commandMap);

            Transport.send(message);

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

    }
}

Context.java

package mail;

public class Context {
    private String subject;
    private String html;
    private StringBuilder builder;

    public Context(String subject) {
        this.subject = subject;
        this.builder = new StringBuilder();
    }

    public Context addLine(String line) {
        builder.append("<p>" + line + "</p>");
        return this;
    }

    public String getSubject() {
        return subject;
    }

    public String getHtml() {
        html = builder.toString();
        return html;
    }

}

결과

사실 보내기까지 에러를 몇 번 뿜었는데, SslHandShakeException이었던 것 같다.
그 오류를 잡으려고 여러 검색을 해 봤는데, starttls속성과 ssl.protocols속성, ssl.enable, ssl.trust 속성을 집어넣으며 해결했다.
그리고 google이메일로 진행하려다가 2중 보안 설정이 되어있는지 안 되어있는지 까먹어서 설정하기 귀찮아..네이버 이메일로 진행했다.

이메일도 잘 오고, 메일 내용, 메일 제목도 잘 적용된다.

후기

구조적으로 잘 짰는진 모르겠지만, 단순히 블로그 글을 그대로 작성하여 테스트 해 보는 것보단 클래스화 해 보면서 코딩 공부를 해 보자는 생각으로 작성한 것이 공부가 되었다.

properties 객체에 설정하는 몇 개의 설정key와 MailcapCommandMap 내용은 이해하지 못 했으나 대충 헤더에 들어가는 내용 등인 것 같다.

메일을 보내는 것도 여러 방법이 있을 것이고, 저런 속성, 헤더에 관한 것도 그렇고 시간 날 때 나중에 더 공부 해 봐야겠다.

그리고 velog 접은 글 어떻게 만드는 지 모르겠다.

0개의 댓글