--- title: SendGrid SDKを使わずSpringのRestClientで直接APIを呼び出してメール送信するメモ tags: ["Spring Boot", "SendGrid", "Testcontainers", "Java"] categories: ["Programming", "Java", "org", "springframework", "boot"] date: 2025-05-19T03:19:47Z updated: 2025-05-19T03:38:45Z --- SendGridのAPIを呼び出すための[Java SDK](https://github.com/sendgrid/sendgrid-java)はあるし、Spring Bootで[AutoConfiguration](https://github.com/spring-projects/spring-boot/blob/v3.4.5/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sendgrid/SendGridAutoConfiguration.java)も用意されていますが、Springの`RestClient`を使って直接APIを呼び出す方法をメモします。 SendGridのJava SDKは * コードが自分の好みでない * 開発・テスト用途の送信先の変更が面倒 (プロパティ一つでエンドポイントを切り替えられない) * 古いApache HttpClientを使っている などの理由で、個人的には使いません。 メールを送信するだけであれば https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send のAPIを実行するだけであり、 基本的な使い方であればシンプルなREST APIアクセスで済むため、わざわざSDKを使わなくてもSpringの`RestClient`を使って直接APIを呼び出すので十分です。 また、`RestClient`を使うと、Interceptorが使えるので * [クライアントサイドのアクセスログ](https://github.com/zalando/logbook) * Observability ([Tracing](https://docs.spring.io/spring-boot/reference/actuator/tracing.html#actuator.micrometer-tracing.propagating-traces), [Metrics](https://docs.spring.io/spring-boot/reference/actuator/metrics.html#actuator.metrics.supported.http-clients)) * [Retry](https://github.com/making/retryable-client-http-request-interceptor) など既存の機能を使え、他のAPI呼び出しと同じ運用ができるメリットがあります。 **目次** ### プロジェクト作成 Spring InitializrでSpring Bootのプロジェクトを作成します。 ```bash curl -s https://start.spring.io/starter.tgz \ -d artifactId=demo-sendgrid \ -d name=demo-sendgrid \ -d baseDir=demo-sendgrid \ -d packageName=com.example \ -d dependencies=web,actuator,configuration-processor,prometheus,testcontainers \ -d type=maven-project \ -d applicationName=DemoSendgridApplication | tar -xzvf - cd demo-sendgrid ``` まずはSendGridのアクセス情報をプロパティ化します。 ```java cat <<'EOF' > src/main/java/com/example/SendGridProps.java package com.example; import java.net.URI; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.DefaultValue; @ConfigurationProperties(prefix = "sendgrid") public record SendGridProps(String apiKey, @DefaultValue("https://api.sendgrid.com") URI baseUrl, @DefaultValue("noreply@example.com") String from) { } EOF ``` メインクラスに`@ConfigurationPropertiesScan`を追加して、`SendGridProps`をスキャンできるようにします。 ```java cat <<'EOF' > src/main/java/com/example/DemoSendgridApplication.java package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication @ConfigurationPropertiesScan public class DemoSendgridApplication { public static void main(String[] args) { SpringApplication.run(DemoSendgridApplication.class, args); } } EOF ``` ここから本題です。`/v3/mail/send` APIの[ドキュメント](https://www.twilio.com/docs/sendgrid/api-reference/mail-send/mail-send)に従って、リクエストボディを作成します。 送信先、件名、本文、返信先を設定するだけであれば、`Map`で十分です。今回は`HelloController`に直接APIアクセスを書きますが、`SendGridSender`クラスなどを作成した方が良いでしょう。 ```java cat <<'EOF' > src/main/java/com/example/HelloController.java package com.example; import java.util.List; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestClient; import org.springframework.web.server.ResponseStatusException; @RestController public class HelloController { private final RestClient restClient; private final SendGridProps props; public HelloController(RestClient.Builder restClientBuilder, SendGridProps props) { this.restClient = restClientBuilder.baseUrl(props.baseUrl()) .defaultHeaders(headers -> headers.setBearerAuth(props.apiKey())) .defaultStatusHandler(__ -> true, (req, res) -> { }) .build(); this.props = props; } @PostMapping(path = "/send") public Map send(@RequestBody Message message) { ResponseEntity response = this.restClient.post() .uri("/v3/mail/send") .contentType(MediaType.APPLICATION_JSON) .body(Map.of("personalizations", List.of(Map.of("to", List.of(Map.of("email", message.to())), "subject", message.subject())), "from", Map.of("email", this.props.from()), "content", List.of(Map.of("type", "text/plain", "value", message.content())))) .retrieve() .toEntity(String.class); if (!response.getStatusCode().is2xxSuccessful()) { throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Failed to send a mail: " + response.getBody()); } return Map.of("message", "Sent"); } public record Message(String to, String subject, String content) { } } EOF ``` ### TestcontainersでSendGridのモックを起動 動作確認やテスト用に実際のSendGrid APIを呼び出すのではなく、モック化サービスを使いたいです。 [SendGrid MailDev](https://github.com/yKanazawa/sendgrid-maildev)はその用途にぴったりでした。 モックSMTPサーバーである[MailDev](https://maildev.github.io/maildev/)のフロントエンドにSendGrid APIをラップしているようです。 amd/arm両方のDockerイメージが用意されているのも使いやすいです。 SendGrid MailDevをTestcontainersで起動するための設定を追加します。 ```java cat <<'EOF' > src/test/java/com/example/TestcontainersConfiguration.java package com.example; import java.time.Duration; import java.time.temporal.ChronoUnit; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.test.context.DynamicPropertyRegistrar; import org.testcontainers.containers.FixedHostPortGenericContainer; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; @TestConfiguration(proxyBeanMethods = false) class TestcontainersConfiguration { @Bean FixedHostPortGenericContainer sendgrid(@Value("${maildev.port:31080}") int maildevPort) { var container = new FixedHostPortGenericContainer<>("ykanazawa/sendgrid-maildev") .withEnv("SENDGRID_DEV_API_SERVER", ":3030") .withEnv("SENDGRID_DEV_API_KEY", "SG.test") .withEnv("SENDGRID_DEV_SMTP_SERVER", "127.0.0.1:1025") .withExposedPorts(3030, 1080) .waitingFor(new LogMessageWaitStrategy().withRegEx(".*sendgrid-dev entered RUNNING state.*") .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))) .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("sendgrid-maildev"))); return maildevPort > 0 ? container.withFixedExposedPort(maildevPort, 1080) : container; } @Bean DynamicPropertyRegistrar dynamicPropertyRegistrar(GenericContainer sendgrid) { return registry -> { registry.add("sendgrid.base-url", () -> "http://127.0.0.1:" + sendgrid.getMappedPort(3030)); registry.add("sendgrid.api-key", () -> "SG.test"); registry.add("maildev.port", () -> sendgrid.getMappedPort(1080)); }; } } EOF ``` メール受信画面をブラウザで確認したいため、MailDevのWeb UIのポートを31080に固定しました。 Testcontainersをローカル開発用途で使うために、`test-run`コマンドでアプリケーションを起動します。 ```bash ./mvnw spring-boot:test-run ``` またはIDEで`src/test/java/com/example/TestDemoSendgridApplication.java`を実行します。 ``` . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.4.5) 2025-05-19T10:46:18.092+09:00 INFO 57122 --- [demo-sendgrid] [ main] com.example.DemoSendgridApplication : Starting DemoSendgridApplication using Java 21.0.6 with PID 57122 (/private/tmp/demo-sendgrid/target/classes started by toshiaki in /private/tmp/demo-sendgrid) 2025-05-19T10:46:18.093+09:00 INFO 57122 --- [demo-sendgrid] [ main] com.example.DemoSendgridApplication : No active profile set, falling back to 1 default profile: "default" 2025-05-19T10:46:18.402+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-05-19T10:46:18.407+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-05-19T10:46:18.407+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.40] 2025-05-19T10:46:18.423+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-05-19T10:46:18.423+09:00 INFO 57122 --- [demo-sendgrid] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 317 ms 2025-05-19T10:46:18.472+09:00 INFO 57122 --- [demo-sendgrid] [ main] org.testcontainers.images.PullPolicy : Image pull policy will be performed by: DefaultPullPolicy() 2025-05-19T10:46:18.473+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.t.utility.ImageNameSubstitutor : Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor') 2025-05-19T10:46:18.479+09:00 INFO 57122 --- [demo-sendgrid] [ main] org.testcontainers.DockerClientFactory : Testcontainers version: 1.20.6 2025-05-19T10:46:18.542+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.t.d.DockerClientProviderStrategy : Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first 2025-05-19T10:46:18.627+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.t.d.DockerClientProviderStrategy : Found Docker environment with local Unix socket (unix:///var/run/docker.sock) 2025-05-19T10:46:18.627+09:00 INFO 57122 --- [demo-sendgrid] [ main] org.testcontainers.DockerClientFactory : Docker host IP address is localhost 2025-05-19T10:46:18.639+09:00 INFO 57122 --- [demo-sendgrid] [ main] org.testcontainers.DockerClientFactory : Connected to docker: Server Version: 27.5.1 API Version: 1.47 Operating System: OrbStack Total Memory: 96439 MB 2025-05-19T10:46:18.679+09:00 INFO 57122 --- [demo-sendgrid] [ main] tc.testcontainers/ryuk:0.11.0 : Creating container for image: testcontainers/ryuk:0.11.0 2025-05-19T10:46:18.699+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.t.utility.RegistryAuthLocator : Credential helper/store (docker-credential-osxkeychain) does not have credentials for https://index.docker.io/v1/ 2025-05-19T10:46:18.781+09:00 INFO 57122 --- [demo-sendgrid] [ main] tc.testcontainers/ryuk:0.11.0 : Container testcontainers/ryuk:0.11.0 is starting: 785a37255ea791b78ae1e2171d33a284d4a76ba532fe6c7ddd1991ab63ec540a 2025-05-19T10:46:18.938+09:00 INFO 57122 --- [demo-sendgrid] [ main] tc.testcontainers/ryuk:0.11.0 : Container testcontainers/ryuk:0.11.0 started in PT0.258628S 2025-05-19T10:46:18.940+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.t.utility.RyukResourceReaper : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit 2025-05-19T10:46:18.940+09:00 INFO 57122 --- [demo-sendgrid] [ main] org.testcontainers.DockerClientFactory : Checking the system... 2025-05-19T10:46:18.940+09:00 INFO 57122 --- [demo-sendgrid] [ main] org.testcontainers.DockerClientFactory : ✔︎ Docker server version should be at least 1.6.0 2025-05-19T10:46:18.942+09:00 INFO 57122 --- [demo-sendgrid] [ main] tc.ykanazawa/sendgrid-maildev:latest : Creating container for image: ykanazawa/sendgrid-maildev:latest 2025-05-19T10:46:18.977+09:00 INFO 57122 --- [demo-sendgrid] [ main] tc.ykanazawa/sendgrid-maildev:latest : Container ykanazawa/sendgrid-maildev:latest is starting: b084cbf0c96ec57eaeca647dd574415ccb89e1fd852dc031e83c7b7d9563bcc8 2025-05-19T10:46:19.147+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDERR: /usr/lib/python3.11/site-packages/supervisor/options.py:474: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security. 2025-05-19T10:46:19.147+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDERR: self.warnings.warn( 2025-05-19T10:46:19.150+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:19,150 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message. 2025-05-19T10:46:19.151+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:19,150 INFO Included extra file "/etc/supervisor/conf.d/app.conf" during parsing 2025-05-19T10:46:19.153+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:19,153 INFO RPC interface 'supervisor' initialized 2025-05-19T10:46:19.154+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:19,153 CRIT Server 'unix_http_server' running without any HTTP authentication checking 2025-05-19T10:46:19.154+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:19,153 INFO supervisord started with pid 1 2025-05-19T10:46:20.162+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:20,158 INFO spawned: 'maildev' with pid 7 2025-05-19T10:46:20.166+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:20,163 INFO spawned: 'sendgrid-dev' with pid 8 2025-05-19T10:46:20.174+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: SENDGRID_DEV_API_SERVER :3030 2025-05-19T10:46:20.175+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: SENDGRID_DEV_API_KEY SG.test 2025-05-19T10:46:20.176+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: SENDGRID_DEV_SMTP_SERVER 127.0.0.1:1025 2025-05-19T10:46:20.176+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: SENDGRID_DEV_SMTP_USERNAME 2025-05-19T10:46:20.176+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: SENDGRID_DEV_SMTP_PASSWORD 2025-05-19T10:46:20.176+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19T10:46:20.176+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: ____ __ 2025-05-19T10:46:20.177+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: / __/___/ / ___ 2025-05-19T10:46:20.177+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: / _// __/ _ \/ _ \ 2025-05-19T10:46:20.177+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: /___/\__/_//_/\___/ v3.3.10-dev 2025-05-19T10:46:20.177+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: High performance, minimalist Go web framework 2025-05-19T10:46:20.178+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: https://echo.labstack.com 2025-05-19T10:46:20.178+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: ____________________________________O/_______ 2025-05-19T10:46:20.178+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: O\ 2025-05-19T10:46:20.179+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: ⇨ http server started on [::]:3030 2025-05-19T10:46:20.377+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: MailDev using directory /tmp/maildev-7 2025-05-19T10:46:20.385+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: MailDev webapp running at http://0.0.0.0:1080/ 2025-05-19T10:46:20.385+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: MailDev SMTP Server running at 0.0.0.0:1025 2025-05-19T10:46:21.394+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:21,391 INFO success: maildev entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) 2025-05-19T10:46:21.394+09:00 INFO 57122 --- [demo-sendgrid] [ream--195085445] sendgrid-maildev : STDOUT: 2025-05-19 01:46:21,392 INFO success: sendgrid-dev entered RUNNING state, process has stayed up for > than 1 seconds (startsecs) 2025-05-19T10:46:21.394+09:00 INFO 57122 --- [demo-sendgrid] [ main] tc.ykanazawa/sendgrid-maildev:latest : Container ykanazawa/sendgrid-maildev:latest started in PT2.452433S 2025-05-19T10:46:21.562+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint beneath base path '/actuator' 2025-05-19T10:46:21.577+09:00 INFO 57122 --- [demo-sendgrid] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2025-05-19T10:46:21.582+09:00 INFO 57122 --- [demo-sendgrid] [ main] com.example.DemoSendgridApplication : Started DemoSendgridApplication in 3.599 seconds (process running for 3.698) 2025-05-19T10:46:31.351+09:00 INFO 57122 --- [demo-sendgrid] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2025-05-19T10:46:31.351+09:00 INFO 57122 --- [demo-sendgrid] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2025-05-19T10:46:31.352+09:00 INFO 57122 --- [demo-sendgrid] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms ``` http://localhost:31080/ でMailDevのWeb UIを開きます。 ![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1852/60fd2403-cd75-4b67-8614-7446bd64a71a.png) APIを呼び出してメールを送信します。 ```bash $ curl -s http://localhost:8080/send --json '{"to":"makingx@example.com","subject":"Hello World!", "content": "This is a test mail!"}' {"message":"Sent"} ``` MailDevのWeb UIでメールを受信したことを確認します。 ![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1852/199a54d0-76b6-4cc6-b86d-eb57bd15faa8.png) ### Integration Testの作成 Testcontainersの設定をそのまま使ってIntegration Testを作成します。 MainDevは受信したメールを返すREST APIがあるので、`/email` APIを呼び出して受信したメールを確認します。 ただし、このAPIはHTTP/2に対応していません。`RestClient`のデフォルトの`ClientHttpRequestFactory`はSpring Boot 3.4ではHTTP/2を使う`JdkClientHttpRequestFactory`を使う設定になっているため、 HTTP/2未対応の`SimpleClientHttpRequestFactory`を使うように`spring.http.client.factory=simple`を指定します。 ```java cat <<'EOF' > src/test/java/com/example/HelloControllerIntegrationTests.java package com.example; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.context.annotation.Import; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestClient; import static org.assertj.core.api.Assertions.assertThat; @Import(TestcontainersConfiguration.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "maildev.port=0", "spring.http.client.factory=simple" }) public class HelloControllerIntegrationTests { RestClient restClient; @LocalServerPort int serverPort; int maildevPort; @BeforeEach void setUp(@Autowired RestClient.Builder restClientBuilder, @Value("${maildev.port}") int maildevPort) { this.restClient = restClientBuilder.defaultStatusHandler(__ -> true, (req, res) -> { }).build(); this.maildevPort = maildevPort; } @Test void testSend() { ResponseEntity response = this.restClient.post() .uri("http://localhost:{port}/send", this.serverPort) .contentType(MediaType.APPLICATION_JSON) .body(""" {"to":"makingx@example.com","subject":"Hello World!", "content": "This is a test mail!"} """) .retrieve() .toEntity(String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isEqualToIgnoringWhitespace(""" {"message":"Sent"} """); List> received = this.restClient.get() .uri("http://localhost:{port}/email", this.maildevPort) .retrieve() .body(new ParameterizedTypeReference<>() { }); assertThat(received).hasSize(1); assertThat(received.get(0)).containsEntry("subject", "Hello World!"); assertThat(received.get(0)).containsEntry("text", "This is a test mail!\n"); assertThat(received.get(0)).containsEntry("to", List.of(Map.of("address", "makingx@example.com", "name", ""))); assertThat(received.get(0)).containsEntry("from", List.of(Map.of("address", "noreply@example.com", "name", ""))); } } EOF ``` テストが成功することを確認してください。 ```bash ./mvnw test ``` ### SendGridに向き先変更 ローカルで動作確認できたので、送信先をSendGridに変更します。 次のコマンドでSendGridのAPIキーと送信元メールアドレスを引数に設定して、アプリケーションを起動します。 ```bash ./mvnw spring-boot:run -Dspring-boot.run.arguments="--sendgrid.api-key=YOUR_SENDGRID_API_KEY --sendgrid.from=YOUR_VERIFIED_SENDER_EMAIL" ``` APIを呼び出してメールを送信します。 ```bash $ curl -s http://localhost:8080/send --json '{"to":"YOUR_REAL_EMAIL","subject":"Hello World!", "content": "This is a test mail!"}' {"message":"Sent"} ``` メールが届くことを確認してください。 ![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1852/ec520a34-6c26-424f-a6fc-f4a25dcc7d0c.png) --- SendGridのAPIを`RestClient`で直接呼び出す方法をメモしました。 Testcontainersを使って、SendGridのモックを起動してテストする方法も紹介しました。