CODE WITH SIBIN

Solving Real Problems with Real Code


Sending Email with Spring Boot and Azure Communication Services

This comprehensive guide will walk you through setting up email sending capabilities in a Spring Boot application using Azure Communication Services (ACS).

Table of Contents

  1. Azure Communication Services Setup
  2. Spring Boot Project Configuration
  3. Implementing Email Service
  4. Testing with Postman
  5. Best Practices and Considerations

1. Azure Communication Services Setup

Prerequisites

  • An active Azure account
  • Azure CLI installed (optional but recommended)
  • Appropriate permissions to create resources in your Azure subscription

Step-by-Step Azure Configuration

  1. Create a Communication Services Resource
    • Log in to the Azure Portal
    • Click "Create a resource" and search for "Communication Services"
    • Click "Create" and fill in:
      • Subscription: Select your subscription
      • Resource group: Create new or select existing
      • Resource name: e.g., "MyEmailService"
      • Data location: Select region closest to your users
    • Click "Review + create" then "Create"
  2. Configure Email Domain
    • Once deployed, go to your Communication Services resource
    • Navigate to "Email" under "Services" in the left menu
    • Click "Add domain"
    • Choose between:
      • Azure Managed Domain (quick start)
      • Bring Your Own Domain (for production with custom domains)
    • For Azure Managed Domain:
      • Provide a subdomain name (e.g., "notifications")
      • Click "Add domain"
      • Verification may take up to 24 hours
  3. Get Connection String
    • In your Communication Services resource
    • Navigate to "Keys" under "Settings"
    • Copy either "Primary connection string" or "Secondary connection string"
    • Store this securely (we'll use it in Spring Boot)
  4. Verify Sender Address (Optional but Recommended)
    • Under "Email" service, go to "Sender addresses"
    • Click "Add sender address"
    • Provide an email address you control
    • Azure will send a verification email
    • Follow the link to verify the address

2. Spring Boot Project Configuration

Prerequisites

  • Java JDK 11 or later
  • Maven or Gradle
  • Spring Boot 2.7.x or 3.x

Project Setup

1. Create a new Spring Boot project

    • Use start.spring.io with these dependencies:
      • Spring Web
      • Lombok (optional but recommended)
      • Azure Communication Services Email (add manually)

    2. Add Azure Communication Services Dependency

    For Maven (pom.xml):

    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-communication-email</artifactId>
        <version>1.0.0</version> <!-- Check for latest version -->
    </dependency>

    For Gradle (build.gradle):

    implementation 'com.azure:azure-communication-email:1.0.0'

    Configure Application Properties

    Add to application.properties:properties

    # Azure Communication Services
    azure.communication.connection-string=your-connection-string
    azure.communication.sender-email=donotreply@yourdomain.azurecomm.net

    Or in application.yml:

    azure:
      communication:
        connection-string: "your-connection-string"
        sender-email: "donotreply@yourdomain.azurecomm.net"

      3. Implementing Email Service 

      Create Configuration Class

      import com.azure.communication.email.EmailClient;
      import com.azure.communication.email.EmailClientBuilder;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class AzureEmailConfig {
      
          @Value("${azure.communication.connection-string}")
          private String connectionString;
      
          @Bean
          public EmailClient emailClient() {
              return new EmailClientBuilder()
                      .connectionString(connectionString)
                      .buildClient();
          }
      }

      Create Email Service Class

      import com.azure.communication.email.models.*;
      import com.azure.core.util.polling.PollResponse;
      import com.azure.core.util.polling.SyncPoller;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.stereotype.Service;
      
      @Service
      @Slf4j
      public class EmailService {
      
          private final EmailClient emailClient;
          
          @Value("${azure.communication.sender-email}")
          private String senderEmail;
      
          public EmailService(EmailClient emailClient) {
              this.emailClient = emailClient;
          }
      
          public String sendEmail(String to, String subject, String htmlContent, String plainTextContent) {
              try {
                  EmailMessage message = new EmailMessage()
                          .setSenderAddress(senderEmail)
                          .setToRecipients(to)
                          .setSubject(subject)
                          .setBody(new EmailBody()
                                  .setHtml(htmlContent)
                                  .setPlainText(plainTextContent));
      
                  SyncPoller<EmailSendResult, EmailSendResult> poller = emailClient.beginSend(message, null);
                  PollResponse<EmailSendResult> pollResponse = poller.waitForCompletion();
                  
                  if (poller.getFinalResult().getStatus() == EmailSendStatus.SUCCEEDED) {
                      log.info("Email sent successfully to {}", to);
                      return "Email sent successfully";
                  } else {
                      log.error("Failed to send email to {}", to);
                      return "Failed to send email";
                  }
              } catch (Exception ex) {
                  log.error("Error sending email: {}", ex.getMessage());
                  throw new RuntimeException("Error sending email", ex);
              }
          }
      
          public String sendEmailWithAttachment(String to, String subject, String htmlContent, 
                                               String plainTextContent, byte[] attachment, 
                                               String attachmentName, String contentType) {
              try {
                  EmailAttachment emailAttachment = new EmailAttachment(attachmentName, contentType, attachment);
      
                  EmailMessage message = new EmailMessage()
                          .setSenderAddress(senderEmail)
                          .setToRecipients(to)
                          .setSubject(subject)
                          .setBody(new EmailBody()
                                  .setHtml(htmlContent)
                                  .setPlainText(plainTextContent))
                          .setAttachments(emailAttachment);
      
                  SyncPoller<EmailSendResult, EmailSendResult> poller = emailClient.beginSend(message, null);
                  poller.waitForCompletion();
                  
                  if (poller.getFinalResult().getStatus() == EmailSendStatus.SUCCEEDED) {
                      log.info("Email with attachment sent successfully to {}", to);
                      return "Email with attachment sent successfully";
                  } else {
                      log.error("Failed to send email with attachment to {}", to);
                      return "Failed to send email with attachment";
                  }
              } catch (Exception ex) {
                  log.error("Error sending email with attachment: {}", ex.getMessage());
                  throw new RuntimeException("Error sending email with attachment", ex);
              }
          }
      }

      Create REST Controller

      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.*;
      import org.springframework.web.multipart.MultipartFile;
      
      import java.io.IOException;
      
      @RestController
      @RequestMapping("/api/email")
      public class EmailController {
      
          private final EmailService emailService;
      
          public EmailController(EmailService emailService) {
              this.emailService = emailService;
          }
      
          @PostMapping("/send")
          public ResponseEntity<String> sendEmail(
                  @RequestParam String to,
                  @RequestParam String subject,
                  @RequestParam String htmlContent,
                  @RequestParam(required = false) String plainTextContent) {
              
              String response = emailService.sendEmail(
                      to, 
                      subject, 
                      htmlContent, 
                      plainTextContent != null ? plainTextContent : "");
              
              return ResponseEntity.ok(response);
          }
      
          @PostMapping("/send-with-attachment")
          public ResponseEntity<String> sendEmailWithAttachment(
                  @RequestParam String to,
                  @RequestParam String subject,
                  @RequestParam String htmlContent,
                  @RequestParam(required = false) String plainTextContent,
                  @RequestParam MultipartFile attachment) throws IOException {
              
              String response = emailService.sendEmailWithAttachment(
                      to, 
                      subject, 
                      htmlContent, 
                      plainTextContent != null ? plainTextContent : "",
                      attachment.getBytes(),
                      attachment.getOriginalFilename(),
                      attachment.getContentType());
              
              return ResponseEntity.ok(response);
          }
      }

      Exception Handling (Optional but Recommended)

      import org.springframework.http.HttpStatus;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.ResponseStatus;
      import org.springframework.web.bind.annotation.RestControllerAdvice;
      
      @RestControllerAdvice
      public class GlobalExceptionHandler {
      
          @ExceptionHandler(RuntimeException.class)
          @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
          public String handleRuntimeException(RuntimeException ex) {
              return "Error occurred: " + ex.getMessage();
          }
      }

      4. Testing with Postman

      Prerequisites

      • Postman installed
      • Spring Boot application running

      Testing the Basic Email Endpoint

      1. Create a new POST request
        • URL: http://localhost:8080/api/email/send
        • Select "form-data" in the Body tab
      2. Add parameters:
        • torecipient@example.com
        • subject: Test Email from Azure
        • htmlContent<h1>Hello from Azure!</h1><p>This is a test email.</p>
        • plainTextContent: Hello from Azure! This is a test email.
      3. Send the request
        • Expected response: "Email sent successfully"
        • Check recipient's inbox for the email

      Testing Email with Attachment

      1. Create a new POST request
        • URL: http://localhost:8080/api/email/send-with-attachment
        • Select "form-data" in the Body tab
      2. Add parameters:
        • torecipient@example.com
        • subject: Test Email with Attachment
        • htmlContent<h1>Hello!</h1><p>Please see attached file.</p>
        • plainTextContent: Hello! Please see attached file.
        • attachment: Select a file to upload
      3. Send the request
        • Expected response: "Email with attachment sent successfully"
        • Check recipient's inbox for the email with attachment

      Testing Edge Cases

      1. Invalid Email Address
        • Send to: "invalid-email"
        • Expected: Error response with validation message
      2. Missing Required Fields
        • Omit the subject field
        • Expected: 400 Bad Request with details of missing fields
      3. Large Attachments
        • Try sending a file > 10MB (Azure limit)
        • Expected: Error response about size limitation

      5. Best Practices and Considerations 

      Performance Optimization

      • Async Processing: Consider making the email sending asynchronous using @Async
      • Batching: For multiple emails, use batch processing
      • Connection Pooling: Azure client handles this internally

      Security Considerations

      • Secure Connection Strings: Never hardcode in source; use Azure Key Vault
      • Input Validation: Validate all email addresses and content
      • Rate Limiting: Implement to prevent abuse of your email service

      Monitoring and Logging

      • Azure Monitor: Set up alerts for email delivery failures
      • Application Insights: Track email sending metrics
      • Logging: As shown in the service, log successes and failures

      Cost Optimization

      • Free Tier: Azure offers free emails per month
      • Monitoring Usage: Track your email usage to avoid surprises
      • Choosing Right SKU: Select appropriate pricing tier based on volume

      Production Readiness

      • Custom Domain: For production, use your own domain instead of Azure subdomain
      • DKIM/DMARC/SPF: Configure these for better email deliverability
      • Feedback Loop: Set up bounce and complaint handling

      Conclusion

      This guide provided a comprehensive walkthrough of setting up email sending capabilities with Spring Boot and Azure Communication Services. You've learned how to:

      1. Configure Azure Communication Services for email
      2. Set up a Spring Boot project with Azure SDK
      3. Implement email sending with and without attachments
      4. Test the service thoroughly with Postman
      5. Apply best practices for production readiness

      With this implementation, you can now reliably send transactional emails from your Spring Boot applications while leveraging Azure's scalable infrastructure.

      Leave a Reply

      Your email address will not be published. Required fields are marked *