diff --git a/apps/user-service/src/main/java/site/icebang/domain/email/controller/EmailTestController.java b/apps/user-service/src/main/java/site/icebang/domain/email/controller/EmailTestController.java new file mode 100644 index 00000000..633e3ee7 --- /dev/null +++ b/apps/user-service/src/main/java/site/icebang/domain/email/controller/EmailTestController.java @@ -0,0 +1,37 @@ +package site.icebang.domain.email.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import lombok.RequiredArgsConstructor; + +import site.icebang.domain.email.dto.EmailRequest; +import site.icebang.domain.email.service.EmailService; + +@RestController +@RequestMapping("/api/v1/email") +@RequiredArgsConstructor +public class EmailTestController { + private final EmailService emailService; + + @GetMapping("/test") + @PreAuthorize("permitAll()") + public ResponseEntity sendTestEmail(@RequestParam String to) { + try { + EmailRequest emailRequest = + EmailRequest.builder() + .to(to) + .subject("IceBang 실제 테스트 이메일") + .body("안녕하세요!\n\nIceBang에서 보내는 실제 Gmail 테스트 이메일입니다.\n\n성공적으로 연동되었습니다!") + .isHtml(false) + .build(); + + emailService.send(emailRequest); + return ResponseEntity.ok("실제 Gmail 테스트 이메일 전송 완료! 받은편지함을 확인하세요!"); + + } catch (Exception e) { + return ResponseEntity.badRequest().body("이메일 전송 실패: " + e.getMessage()); + } + } +} diff --git a/apps/user-service/src/main/java/site/icebang/domain/email/service/EmailServiceImpl.java b/apps/user-service/src/main/java/site/icebang/domain/email/service/EmailServiceImpl.java index cd8ba706..1bf7454b 100644 --- a/apps/user-service/src/main/java/site/icebang/domain/email/service/EmailServiceImpl.java +++ b/apps/user-service/src/main/java/site/icebang/domain/email/service/EmailServiceImpl.java @@ -1,16 +1,92 @@ -// package site.icebang.domain.email.service; -// -// import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -// import org.springframework.stereotype.Service; -// -// import lombok.RequiredArgsConstructor; -// -// import site.icebang.domain.email.dto.EmailRequest; -// -// @Service -// @RequiredArgsConstructor +package site.icebang.domain.email.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import site.icebang.domain.email.dto.EmailRequest; + +@Slf4j +@Service +@Profile({"production"}) +@RequiredArgsConstructor // @ConditionalOnMissingBean(EmailService.class) -// public class EmailServiceImpl implements EmailService { -// @Override -// public void send(EmailRequest emailRequest) {} -// } +public class EmailServiceImpl implements EmailService { + + private final JavaMailSender mailSender; + + @Value("${spring.mail.username}") + private String defaultSender; + + @Override + public void send(EmailRequest request) { + try { + if (request.isHtml()) { + sendHtmlEmail(request); + } else { + sendSimpleEmail(request); + } + } catch (Exception e) { + log.error("❌ 이메일 전송 실패 - To: {}", request.getTo(), e); + throw new RuntimeException("이메일 전송 실패: " + e.getMessage(), e); + } + } + + private void sendSimpleEmail(EmailRequest request) { + try { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(request.getTo()); + message.setSubject(request.getSubject()); + message.setText(request.getBody()); + message.setFrom(defaultSender); + + // CC 설정 + if (request.getCc() != null && !request.getCc().isEmpty()) { + message.setCc(request.getCc().toArray(new String[0])); + } + + // BCC 설정 + if (request.getBcc() != null && !request.getBcc().isEmpty()) { + message.setBcc(request.getBcc().toArray(new String[0])); + } + + mailSender.send(message); + log.info("✅ 실제 Gmail 전송 성공! To: {}, Subject: {}", request.getTo(), request.getSubject()); + + } catch (Exception e) { + log.error("❌ Gmail 텍스트 전송 실패", e); + throw e; + } + } + + private void sendHtmlEmail(EmailRequest request) throws MessagingException { + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); + + helper.setTo(request.getTo()); + helper.setSubject(request.getSubject()); + helper.setText(request.getBody(), true); // HTML 모드 + helper.setFrom(defaultSender); + + // CC 설정 + if (request.getCc() != null && !request.getCc().isEmpty()) { + helper.setCc(request.getCc().toArray(new String[0])); + } + + // BCC 설정 + if (request.getBcc() != null && !request.getBcc().isEmpty()) { + helper.setBcc(request.getBcc().toArray(new String[0])); + } + + mailSender.send(message); + log.info("✅ 실제 Gmail HTML 전송 성공! To: {}, Subject: {}", request.getTo(), request.getSubject()); + } +} diff --git a/apps/user-service/src/main/java/site/icebang/domain/email/service/MockEmailService.java b/apps/user-service/src/main/java/site/icebang/domain/email/service/MockEmailService.java index 7530667d..ee84e8ea 100644 --- a/apps/user-service/src/main/java/site/icebang/domain/email/service/MockEmailService.java +++ b/apps/user-service/src/main/java/site/icebang/domain/email/service/MockEmailService.java @@ -1,5 +1,6 @@ package site.icebang.domain.email.service; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; @@ -7,7 +8,7 @@ import site.icebang.domain.email.dto.EmailRequest; @Service -// @Profile({"test-unit", "test-e2e", "test-integration", "local", "develop", "production"}) +@Profile({"develop", "test-e2e", "test-integration", "test-unit"}) @Slf4j public class MockEmailService implements EmailService { diff --git a/apps/user-service/src/main/java/site/icebang/global/config/security/SecurityConfig.java b/apps/user-service/src/main/java/site/icebang/global/config/security/SecurityConfig.java index 8b5dd63c..3543a8dd 100644 --- a/apps/user-service/src/main/java/site/icebang/global/config/security/SecurityConfig.java +++ b/apps/user-service/src/main/java/site/icebang/global/config/security/SecurityConfig.java @@ -78,6 +78,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .permitAll() .requestMatchers("/v0/workflows/**") .permitAll() + .requestMatchers("/api/v1/email/**") // 이메일 API 경로 허용 + .permitAll() .requestMatchers("/v0/auth/check-session") .authenticated() .requestMatchers(SecurityEndpoints.DATA_ADMIN.getMatchers()) diff --git a/apps/user-service/src/main/resources/application-production.yml b/apps/user-service/src/main/resources/application-production.yml index 032954ad..c3645e13 100644 --- a/apps/user-service/src/main/resources/application-production.yml +++ b/apps/user-service/src/main/resources/application-production.yml @@ -17,6 +17,22 @@ spring: minimum-idle: 5 pool-name: HikariCP-MyBatis + # Gmail 연동 설정 + mail: + host: smtp.gmail.com + port: 587 + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + connectiontimeout: 5000 + timeout: 5000 + writetimeout: 5000 + debug: true # quartz: # jdbc: # initialize-schema: never