eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

Sending emails is an important feature for modern web applications, whether it’s for user registration, password resets, or promotional campaigns.

In this tutorial, we’ll explore how to send emails using SendGrid within a Spring Boot application. We’ll walk through the necessary configurations and implement email-sending functionality for different use cases.

2. Setting up SendGrid

To follow this tutorial, we’ll first need a SendGrid account. SendGrid offers a free tier that allows us to send up to 100 emails per day, which is sufficient for our demonstration.

Once we’ve signed up, we’ll need to create an API key to authenticate our requests to the SendGrid service.

Finally, we’ll need to verify our sender identity to send emails successfully.

3. Setting up the Project

Before we can start sending emails with SendGrid, we’ll need to include an SDK dependency and configure our application correctly.

3.1. Dependencies

Let’s start by adding the SendGrid SDK dependency to our project’s pom.xml file:

<dependency>
    <groupId>com.sendgrid</groupId>
    <artifactId>sendgrid-java</artifactId>
    <version>4.10.2</version>
</dependency>

This dependency provides us with the necessary classes to interact with the SendGrid service and send emails from our application.

3.2. Defining SendGrid Configuration Properties

Now, to interact with the SendGrid service and send emails to our users, we need to configure our API key to authenticate API requests. We’ll also need to configure the sender’s name and email address, which should match the sender identity we’ve set up in our SendGrid account.

We’ll store these properties in our project’s application.yaml file and use @ConfigurationProperties to map the values to a POJO, which our service layer references when interacting with SendGrid:

@Validated
@ConfigurationProperties(prefix = "com.baeldung.sendgrid")
class SendGridConfigurationProperties {
    @NotBlank
    @Pattern(regexp = "^SG[0-9a-zA-Z._]{67}$")
    private String apiKey;

    @Email
    @NotBlank
    private String fromEmail;

    @NotBlank
    private String fromName;

    // standard setters and getters
}

We’ve also added validation annotations to ensure all the required properties are configured correctly. If any of the defined validations fail, the Spring ApplicationContext will fail to start up. This allows us to conform to the fail-fast principle.

Below is a snippet of our application.yaml file, which defines the required properties that’ll be mapped to our SendGridConfigurationProperties class automatically:

com:
  baeldung:
    sendgrid:
      api-key: ${SENDGRID_API_KEY}
      from-email: ${SENDGRID_FROM_EMAIL}
      from-name: ${SENDGRID_FROM_NAME}

We use the ${} property placeholder to load the values of our properties from environment variables. Accordingly, this setup allows us to externalize the SendGrid properties and easily access them in our application.

3.3. Configuring SendGrid Beans

Now that we’ve configured our properties, let’s reference them to define the necessary beans:

@Configuration
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class SendGridConfiguration {
    private final SendGridConfigurationProperties sendGridConfigurationProperties;

    // standard constructor

    @Bean
    public SendGrid sendGrid() {
        String apiKey = sendGridConfigurationProperties.getApiKey();
        return new SendGrid(apiKey);
    }
}

Using constructor injection, we inject an instance of our SendGridConfigurationProperties class we created earlier. Then we use the configured API key to create a SendGrid bean.

Next, we’ll create a bean to represent the sender for all our outgoing emails:

@Bean
public Email fromEmail() {
    String fromEmail = sendGridConfigurationProperties.getFromEmail();
    String fromName = sendGridConfigurationProperties.getFromName();
    return new Email(fromEmail, fromName);
}

With these beans in place, we can autowire them in our service layer to interact with the SendGrid service.

4. Sending Simple Emails

Now that we’ve defined our beans, let’s create an EmailDispatcher class and reference them to send a simple email:

private static final String EMAIL_ENDPOINT = "mail/send";

public void dispatchEmail(String emailId, String subject, String body) {
    Email toEmail = new Email(emailId);
    Content content = new Content("text/plain", body);
    Mail mail = new Mail(fromEmail, subject, toEmail, content);

    Request request = new Request();
    request.setMethod(Method.POST);
    request.setEndpoint(EMAIL_ENDPOINT);
    request.setBody(mail.build());

    sendGrid.api(request);
}

In our dispatchEmail() method, we create a new Mail object that represents the email we want to send, then set it as the request body of our Request object.

Finally, we use the SendGrid bean to send the request to the SendGrid service.

5. Sending Emails with Attachments

In addition to sending simple plain text emails, SendGrid also allows us to send emails with attachments.

First, we’ll create a helper method to convert a MultipartFile to an Attachments object from the SendGrid SDK:

private Attachments createAttachment(MultipartFile file) {
    byte[] encodedFileContent = Base64.getEncoder().encode(file.getBytes());
    Attachments attachment = new Attachments();
    attachment.setDisposition("attachment");
    attachment.setType(file.getContentType());
    attachment.setFilename(file.getOriginalFilename());
    attachment.setContent(new String(encodedFileContent, StandardCharsets.UTF_8));
    return attachment;
}

In our createAttachment() method, we’re creating a new Attachments object and setting its properties based on the MultipartFile parameter.

It’s important to note that we Base64 encode the file’s content before setting it in the Attachments object.

Next, let’s update our dispatchEmail() method to accept an optional list of MultipartFile objects:

public void dispatchEmail(String emailId, String subject, String body, List<MultipartFile> files) {
    // ... same as above

    if (files != null && !files.isEmpty()) {
        for (MultipartFile file : files) {
            Attachments attachment = createAttachment(file);
            mail.addAttachments(attachment);
        }
    }

    // ... same as above
}

We iterate over each file in our files parameter, create its corresponding Attachments object using our createAttachment() method, and add it to our Mail object. The rest of the method remains the same.

6. Sending Emails with Dynamic Templates

SendGrid also allows us to create dynamic email templates using HTML and Handlebars syntax.

For this demonstration, we’ll take an example where we want to send a personalized hydration alert email to our users.

6.1. Creating the HTML Template

First, we’ll create an HTML template for our hydration alert email:

<html>
    <head>
        <style>
            body { font-family: Arial; line-height: 2; text-align: Center; }
            h2 { color: DeepSkyBlue; }
            .alert { background: Red; color: White; padding: 1rem; font-size: 1.5rem; font-weight: bold; }
            .message { border: .3rem solid DeepSkyBlue; padding: 1rem; margin-top: 1rem; }
            .status { background: LightCyan; padding: 1rem; margin-top: 1rem; }
        </style>
    </head>
    <body>
        <div class="alert">⚠️ URGENT HYDRATION ALERT ⚠️</div>
        <div class="message">
            <h2>It's time to drink water!</h2>
            <p>Hey {{name}}, this is your friendly reminder to stay hydrated. Your body will thank you!</p>
            <div class="status">
                <p><strong>Last drink:</strong> {{lastDrinkTime}}</p>
                <p><strong>Hydration status:</strong> {{hydrationStatus}}</p>
            </div>
        </div>
    </body>
</html>

In our template, we’re using Handlebars syntax to define placeholders of {{name}}, {{lastDrinkTime}}, and {{hydrationStatus}}. We’ll replace these placeholders with actual values when we send the email.

We also use internal CSS to beautify our email template.

6.2. Configuring the Template ID

Once we create our template in SendGrid, it’s assigned a unique template ID.

To hold this template ID, we’ll define a nested class inside our SendGridConfigurationProperties class:

@Valid
private HydrationAlertNotification hydrationAlertNotification = new HydrationAlertNotification();

class HydrationAlertNotification {
    @NotBlank
    @Pattern(regexp = "^d-[a-f0-9]{32}$")
    private String templateId;

    // standard setter and getter
}

We again add validation annotations to ensure that we configure the template ID correctly and it matches the expected format.

Similarly, let’s add the corresponding template ID property to our application.yaml file:

com:
  baeldung:
    sendgrid:
      hydration-alert-notification:
        template-id: ${HYDRATION_ALERT_TEMPLATE_ID}

We’ll use this configured template ID in our EmailDispatcher class when sending our hydration alert email.

6.3. Sending Templated Emails

Now that we’ve configured our template ID, let’s create a custom Personalization class to hold our placeholder key names and their corresponding values:

class DynamicTemplatePersonalization extends Personalization {
    private final Map<String, Object> dynamicTemplateData = new HashMap<>();

    public void add(String key, String value) {
        dynamicTemplateData.put(key, value);
    }

    @Override
    public Map<String, Object> getDynamicTemplateData() {
        return dynamicTemplateData;
    }
}

We override the getDynamicTemplateData() method to return our dynamicTemplateData map, which we populate using the add() method.

Now, let’s create a new service method to send out our hydration alerts:

public void dispatchHydrationAlert(String emailId, String username) {
    Email toEmail = new Email(emailId);
    String templateId = sendGridConfigurationProperties.getHydrationAlertNotification().getTemplateId();

    DynamicTemplatePersonalization personalization = new DynamicTemplatePersonalization();
    personalization.add("name", username);
    personalization.add("lastDrinkTime", "Way too long ago");
    personalization.add("hydrationStatus", "Thirsty as a camel");
    personalization.addTo(toEmail);

    Mail mail = new Mail();
    mail.setFrom(fromEmail);
    mail.setTemplateId(templateId);
    mail.addPersonalization(personalization);

    // ... sending request process same as previous   
}

In our dispatchHydrationAlert() method, we create an instance of our DynamicTemplatePersonalization class and add custom values for the placeholders we defined in our HTML template.

Then, we set this personalization object along with the templateId on the Mail object before sending the request to SendGrid.

SendGrid will replace the placeholders in our HTML template with the provided dynamic data. This helps us to send personalized emails to our users while maintaining a consistent design and layout.

7. Testing the SendGrid Integration

Now that we’ve implemented the functionality to send emails using SendGrid, let’s look at how we can test this integration.

Testing external services can be challenging, as we don’t want to make actual API calls to SendGrid during our tests. This is where we’ll use MockServer, which will allow us to simulate the outgoing SendGrid calls.

7.1. Configuring the Test Environment

Before we write our test, we’ll create an application-integration-test.yaml file in our src/test/resources directory with the following content:

com:
  baeldung:
    sendgrid:
      api-key: SG0101010101010101010101010101010101010101010101010101010101010101010
      from-email: [email protected]
      from-name: Baeldung
      hydration-alert-notification:
        template-id: d-01010101010101010101010101010101

These dummy values bypass the validation we’d configured earlier in our SendGridConfigurationProperties class.

Now, let’s set up our test class:

@SpringBootTest
@ActiveProfiles("integration-test")
@MockServerTest("server.url=http://localhost:${mockServerPort}")
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class EmailDispatcherIntegrationTest {
    private MockServerClient mockServerClient;

    @Autowired
    private EmailDispatcher emailDispatcher;
    
    @Autowired
    private SendGridConfigurationProperties sendGridConfigurationProperties;
    
    private static final String SENDGRID_EMAIL_API_PATH = "/v3/mail/send";
}

We use the @ActiveProfiles annotation to load our integration-test-specific properties.

We also use the @MockServerTest annotation to start an instance of MockServer and create a server.url test property with the ${mockServerPort} placeholder. This is replaced by the chosen free port for MockServer, which we’ll reference in the next section where we configure our custom SendGrid REST client.

7.2. Configuring Custom SendGrid REST Client

In order to route our SendGrid API requests to MockServer, we need to configure a custom REST client for the SendGrid SDK.

We’ll create a @TestConfiguration class that defines a new SendGrid bean with a custom HttpClient:

@TestConfiguration
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class TestSendGridConfiguration {
    @Value("${server.url}")
    private URI serverUrl;

    @Autowired
    private SendGridConfigurationProperties sendGridConfigurationProperties;

    @Bean
    @Primary
    public SendGrid testSendGrid() {
        SSLContext sslContext = SSLContextBuilder.create()
          .loadTrustMaterial((chain, authType) -> true)
          .build();

        HttpClientBuilder clientBuilder = HttpClientBuilder.create()
          .setSSLContext(sslContext)
          .setProxy(new HttpHost(serverUrl.getHost(), serverUrl.getPort()));

        Client client = new Client(clientBuilder.build(), true);
        client.buildUri(serverUrl.toString(), null, null);

        String apiKey = sendGridConfigurationProperties.getApiKey();
        return new SendGrid(apiKey, client);
    }
}

In our TestSendGridConfiguration class, we create a custom Client that routes all requests through a proxy server specified by the server.url property. We also configure the SSL context to trust all certificates, as MockServer uses a self-signed certificate by default.

To use this test configuration in our integration test, we need to add the @ContextConfiguration annotation to our test class:

@ContextConfiguration(classes = TestSendGridConfiguration.class)

This ensures that our application uses the bean we’ve defined in our TestSendGridConfiguration class instead of the one we’ve defined in our SendGridConfiguration class when running integration tests.

7.3. Validating the SendGrid Request

Finally, let’s write a test case to verify that our dispatchEmail() method sends the expected request to SendGrid:

// Set up test data
String toEmail = RandomString.make() + "@baeldung.it";
String emailSubject = RandomString.make();
String emailBody = RandomString.make();
String fromName = sendGridConfigurationProperties.getFromName();
String fromEmail = sendGridConfigurationProperties.getFromEmail();
String apiKey = sendGridConfigurationProperties.getApiKey();

// Create JSON body
String jsonBody = String.format("""
    {
        "from": {
            "name": "%s",
            "email": "%s"
        },
        "subject": "%s",
        "personalizations": [{
            "to": [{
                "email": "%s"
            }]
        }],
        "content": [{
            "value": "%s"
        }]
    }
    """, fromName, fromEmail, emailSubject, toEmail, emailBody);

// Configure mock server expectations
mockServerClient
  .when(request()
    .withMethod("POST")
    .withPath(SENDGRID_EMAIL_API_PATH)
    .withHeader("Authorization", "Bearer " + apiKey)
    .withBody(new JsonBody(jsonBody, MatchType.ONLY_MATCHING_FIELDS)
  ))
  .respond(response().withStatusCode(202));

// Invoke method under test
emailDispatcher.dispatchEmail(toEmail, emailSubject, emailBody);

// Verify the expected request was made
mockServerClient
  .verify(request()
    .withMethod("POST")
    .withPath(SENDGRID_EMAIL_API_PATH)
    .withHeader("Authorization", "Bearer " + apiKey)
    .withBody(new JsonBody(jsonBody, MatchType.ONLY_MATCHING_FIELDS)
  ), VerificationTimes.once());

In our test method, we first set up the test data and create the expected JSON body for the SendGrid request. We then configure MockServer to expect a POST request to the SendGrid API path with the Authorization header and JSON body. We also instruct MockServer to respond with a 202 status code when this request is made.

Next, we invoke our dispatchEmail() method with the test data and verify that the expected request was made to MockServer exactly once.

By using MockServer to simulate the SendGrid API, we ensure that our integration works as expected without actually sending any emails or incurring any costs.

8. Conclusion

In this article, we explored how to send emails using SendGrid from a Spring Boot application.

We walked through the necessary configurations and implemented the functionality to send simple emails, emails with attachments, and HTML emails with dynamic templates.

Finally, to validate that our application sends the correct request to SendGrid, we wrote an integration test using MockServer.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)