📧 Gmail API Email Sender

Spring Boot application for sending emails via Gmail API with step-by-step setup guide

⚙️ Setup Instructions

Follow these steps to enable Gmail API and generate credentials

Google Cloud Console Setup

  1. 1
    Create Google Cloud Project

    Visit Google Cloud Console

    Click "Select a Project" → "New Project" → Enter "Gmail Sender App" → Click "Create"

  2. 2
    Enable Gmail API

    Search for "Gmail API" in the search bar

    Click on "Gmail API" → Click "ENABLE"

  3. 3
    Create OAuth 2.0 Credentials

    Go to "APIs & Services" → "Credentials"

    Click "Create Credentials" → Select "OAuth Client ID"

    Choose "Desktop application" → Click "Create"

    Download the JSON file and rename to credentials.json

  4. 4
    Configure OAuth Consent Screen

    Go to "APIs & Services" → "OAuth consent screen"

    Select "External" user type → Click "Create"

    Fill in app name, user support email, and developer contact

    Add https://mail.google.com/ scope (or use search for Gmail)

  5. 5
    Generate Refresh Token

    Run the Spring Boot app once to trigger OAuth flow

    Follow the authorization URL to grant permissions

    App will save TokenCache.json with refresh token

Spring Boot Application Setup

  1. 1
    Create Maven Project

    Use Spring Initializr or generate with Maven:

    mvn archetype:generate -DgroupId=com.example -DartifactId=gmail-sender -DarchetypeArtifactId=maven-archetype-quickstart
  2. 2
    Add Dependencies to pom.xml

    Include Gmail API and Spring Boot dependencies

  3. 3
    Place credentials.json

    Copy credentials.json to project root or src/main/resources/

  4. 4
    Configure application.properties

    Set sender email and application name (see code section)

  5. 5
    Run the Application

    Execute: mvn spring-boot:run

    First run will prompt for authorization

✉️ Send Email

Test sending emails after completing setup

API Endpoint:

POST /api/email/send Content-Type: application/json { "to": "recipient@example.com", "subject": "Email Subject", "body": "Email message", "isHtml": false }

💻 Code Implementation

Complete source code files for the Spring Boot application

pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>gmail-sender</artifactId> <version>1.0.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Gmail API --> <dependency> <groupId>com.google.apis</groupId> <artifactId>google-api-services-gmail</artifactId> <version>v1-rev20230810-2.0.0</version> </dependency> <!-- Google Auth Library --> <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> <version>1.11.0</version> </dependency> <!-- Google Auth API Client --> <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-appengine</artifactId> <version>1.11.0</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- JSON Processing (Jackson) --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- Test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

GmailConfig.java

package com.example.gmail.config; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.util.store.FileDataStoreFactory; import com.google.api.services.gmail.Gmail; import com.google.api.services.gmail.GmailScopes; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.security.GeneralSecurityException; import java.util.Collections; import java.util.List; @Configuration public class GmailConfig { private static final String APPLICATION_NAME = "Gmail API Spring Boot"; private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; private static final String TOKENS_DIRECTORY_PATH = "tokens"; private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_SEND); @Bean public Gmail gmailService() throws GeneralSecurityException, IOException { NetHttpTransport httpTransport = new NetHttpTransport(); return new Gmail.Builder( httpTransport, JSON_FACTORY, getCredentials(httpTransport)) .setApplicationName(APPLICATION_NAME) .build(); } private Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { InputStream in = GmailConfig.class.getResourceAsStream(CREDENTIALS_FILE_PATH); if (in == null) { throw new IOException("credentials.json not found in resources!"); } GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) .setDataStoreFactory( new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) .setAccessType("offline") .build(); LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); return new AuthorizationCodeInstalledApp(flow, receiver) .authorize("user"); } }

EmailRequest.java & EmailResponse.java

// EmailRequest.java package com.example.gmail.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class EmailRequest { private String to; private String subject; private String body; private boolean isHtml; } // EmailResponse.java package com.example.gmail.dto; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class EmailResponse { private boolean success; private String message; private String messageId; private String error; }

GmailService.java

package com.example.gmail.service; import com.example.gmail.dto.EmailRequest; import com.example.gmail.dto.EmailResponse; import com.google.api.services.gmail.Gmail; import com.google.api.services.gmail.model.Message; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.mail.Session; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.io.ByteArrayOutputStream; import java.util.Base64; import java.util.Properties; @Slf4j @Service @RequiredArgsConstructor public class GmailService { private final Gmail gmail; @Value("${gmail.sender.email}") private String senderEmail; public EmailResponse sendEmail(EmailRequest request) { try { MimeMessage mimeMessage = createMimeMessage(request); String encodedMessage = encodeMessage(mimeMessage); Message message = new Message(); message.setRaw(encodedMessage); Message sentMessage = gmail.users() .messages() .send("me", message) .execute(); log.info("Email sent successfully. Message ID: {}", sentMessage.getId()); return EmailResponse.builder() .success(true) .message("Email sent successfully") .messageId(sentMessage.getId()) .build(); } catch (Exception e) { log.error("Failed to send email", e); return EmailResponse.builder() .success(false) .message("Failed to send email") .error(e.getMessage()) .build(); } } private MimeMessage createMimeMessage(EmailRequest request) throws Exception { Properties props = new Properties(); Session session = Session.getDefaultInstance(props, null); MimeMessage mimeMessage = new MimeMessage(session); mimeMessage.setFrom(new InternetAddress(senderEmail)); mimeMessage.addRecipient( javax.mail.Message.RecipientType.TO, new InternetAddress(request.getTo())); mimeMessage.setSubject(request.getSubject()); if (request.isHtml()) { mimeMessage.setContent( request.getBody(), "text/html; charset=utf-8"); } else { mimeMessage.setText(request.getBody()); } return mimeMessage; } private String encodeMessage(MimeMessage message) throws Exception { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); message.writeTo(buffer); byte[] bytes = buffer.toByteArray(); return Base64.getUrlEncoder() .withoutPadding() .encodeToString(bytes); } }

EmailController.java

package com.example.gmail.controller; import com.example.gmail.dto.EmailRequest; import com.example.gmail.dto.EmailResponse; import com.example.gmail.service.GmailService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/email") @RequiredArgsConstructor public class EmailController { private final GmailService gmailService; @PostMapping("/send") public ResponseEntity<EmailResponse> sendEmail( @RequestBody EmailRequest emailRequest) { if (!isValidEmail(emailRequest.getTo())) { return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(EmailResponse.builder() .success(false) .message("Invalid recipient email address") .build()); } EmailResponse response = gmailService.sendEmail(emailRequest); return ResponseEntity .status(response.isSuccess() ? HttpStatus.OK : HttpStatus.INTERNAL_SERVER_ERROR) .body(response); } private boolean isValidEmail(String email) { String emailPattern = "^[A-Za-z0-9+_.-]+@(.+)$"; return email != null && email.matches(emailPattern); } }

application.properties

# Server Configuration server.port=8080 server.servlet.context-path=/ # Application Name spring.application.name=Gmail Sender API # Gmail Configuration gmail.sender.email=your-email@gmail.com # Logging logging.level.root=INFO logging.level.com.example.gmail=DEBUG

🔧 Troubleshooting & Common Issues

Solutions for common problems during setup and usage

✓ Pre-deployment Checklist

Common Issues & Solutions:

❌ Error: "credentials.json not found"

Download credentials.json from Google Cloud Console and place it in src/main/resources/ directory

❌ Error: "invalid_grant" or "Access Denied"

Delete TokenCache.json and tokens/ folder. Run app again to re-authenticate.

❌ Error: "The caller does not have permission"

Ensure GmailScopes.GMAIL_SEND is configured in GmailConfig.java

❌ Email not sending silently

Check logs for detailed error messages. Enable DEBUG logging in application.properties

❌ Port 8888 already in use (OAuth callback)

Modify LocalServerReceiver port in GmailConfig.java or close the application using port 8888

⭐ Key Features & Best Practices

Features

  • ✅ OAuth 2.0 Authentication
  • ✅ Plain text & HTML emails
  • ✅ Error handling & logging
  • ✅ Thread-safe implementation
  • ✅ Auto token refresh
  • ✅ RESTful API endpoint

Best Practices

  • 🔐 Store credentials securely
  • 📝 Validate email addresses
  • ⏱️ Implement rate limiting
  • 📊 Log all email activities
  • 🔄 Use offline refresh tokens
  • ⚠️ Handle exceptions gracefully