Welcome to Part 8 of this series of guides on how to integrate Gmail with Angular and Spring Boot.

You've made a lot of progress, but you still haven't done anything on the Angular side of the house just yet.

But you did accomplish quite a bit with the microservice.

And, you're still going to stay with the microservice for this guide. 

It's okay, though. Good things come to those who wait.

But if you don't want to wait to go through the whole series, you can take a look at the code on GitHub.

Otherwise, stay right here.

What We Have Here

Thus far in the series, you have:

  • Created an OAuth2 client ID and secret on Google Cloud Console
  • Enabled the Gmail API via Google Cloud Console
  • Set the necessary properties in your Spring Boot application.properties file 
  • Set up a DataStore implementation with its associated factory
  • Set up a refresh listener
  • Created a utility class that handles the Google authorization code flow
  • Set up proper Gmail consent with the Google Cloud Platform
  • Created a controller that handles a request to get the Google authorization code flow URL
  • Used Postman to get the Google authorization code URL
  • Used that URL to authorize your application to access your Gmail inbox
  • Updated the controller so it handles a token request
  • Updated the utility class so it creates the credential from a token
  • Persisted that credential
  • Used Postman to retrieve the token
  • Learned why you didn't actually need to retrieve the token
  • Learned about the structure of the Message object
  • Learned about MIME

And if you haven't done those things, then you should take a look at Part 1 and work forward from there.

What We'll Soon Have Here

In this part of the series, you will:

  • Create an Email type that gets sent back to the client
  • Write code that will translate a Message object to an Email object

And then you can go back to watching Mayans.

Your Own Quiet Email Type

In the previous guide, I showed you what the Message object looks like.

It was big, bad, and ugly.

What I mean by that is: your users probably don't need all that information.

You probably just want to take the really important data and send it back to the skinnified email client within your Angular app.

In other words, you want stuff like:

  • The body of the message
  • The sender's name and email address
  • The recipients' names and emaill addresses
  • The date and time the message was sent
  • The subject of the message
  • The ID of the message

You probably don't need all the SMTP relay info unless you're going for something really sophisticated.

That's why you'll create a custom Email class. It will act as a flyweight version of the much larger Message class.

Also: that Email object will include decoded bodies retrieved from the original Message objects.

So, with that in mind, here's the Email class:

public class Email {

	private String html;
	private String plainText;
	private String subject;
	private Long date;
	private String from;
	private String to;
	private String id;

	public String getHtml() {
		return html;
	}

	public void setHtml(String html) {
		this.html = html;
	}
	
	public String getPlainText() {
		return plainText;
	}

	public void setPlainText(String plainText) {
		this.plainText = plainText;
	}
	
	public String getSubject() {
		return subject;
	}

	public void setSubject(String subject) {
		this.subject = subject;
	}

	public Long getDate() {
		return date;
	}

	public void setDate(Long date) {
		this.date = date;
	}

	public String getFrom() {
		return from;
	}

	public void setFrom(String from) {
		this.from = from;
	}

	public String getTo() {
		return to;
	}

	public void setTo(String to) {
		this.to = to;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String toString() {
		return ReflectionToStringBuilder.toString(this);
	}
}

Yep. That's it.

It's just a POJO with important email properties.

Also, note that the object includes both plain text and HTML versions of the email message. That could get a bit heavyweight if you're expecting large messages. You might want to include one or the other.

Next, let's take a look at how you'll use this class.

GmailUtil: The New Utility Belt

Meet the new utility belt: GmailUtil. It's going to include a bunch of convenience methods for parsing responses from the Gmail API.

Get the ball rolling by creating a method that translates the Message object into an Email object.

    public Email getEmail(Message message) {
        Email email = new Email();
        
        email.setHtml(getHtmlBody(message));
        email.setPlainText(getPlainText(message));
        
        if (StringUtils.isEmpty(email.getHtml()) && StringUtils.isEmpty(email.getPlainText())) {
            email.setPlainText(getData(message));
        }
        
        email.setId(message.getId());
        
        setValuesFromHeaders(email, message);
        
        return email;
    }

That method starts off by creating an empty instance of the Email class you just created.

Next, the code sets the html and plainText properties of that object. I'll cover the methods that handle those tasks in a moment.

That if check you see sets the plain text if the Message does not use multipart/alternative format. In that case, there's only one MessagePart so the code doesn't need to iterate through a List of MessagePart objects. 

Next, the method sets the id property of the Email object from the id of the Message object.

Finally, the method invokes setValuesFromHeader() to set the rest of the properties. I'll cover that method in a moment.

In fact, I'll cover it now:

    private void setValuesFromHeaders(Email email, Message message) {
        List<MessagePartHeader> headers = message.getPayload().getHeaders();
        
        headers.forEach(header -> {
            setValueFromHeader(header, email);
        });
    }
    
    
    private void setValueFromHeader(MessagePartHeader header, Email email) {
        if (header.getName() != null) {
            switch (header.getName()) {
                case  SUBJECT_HEADER:
                    email.setSubject(header.getValue());
                    break;
                case  FROM_HEADER:
                    email.setFrom(header.getValue());
                    break;
                case  TO_HEADER:
                    email.setTo(header.getValue());
                    break;
                case  DATE_HEADER:
                    email.setDate(DateUtil.getLongValueFromGmailDateFormat(header.getValue()));
                    break;
            }
        }
    }

So here's the bottom line: a lot of the important info about the email, such as who it's from and the date it was sent, gets stored in the MessagePartHeader array that's MessagePart object. So the code needs to step through that array to grab the required data.

And that's exactly what you see happening above between those two methods. 

Next, take a look at the getHtmlBody() method (also referenced above):

    private String getHtmlBody(Message message) {
        StringBuilder sb = new StringBuilder();
        
        if (message.getPayload() != null && message.getPayload().getParts() != null) {
            for (MessagePart msgPart : message.getPayload().getParts()) {
                if (msgPart.getMimeType().contains("text/html"))
                    sb.append((new String(Base64.getUrlDecoder().decode(msgPart.getBody().getData()))));
            }           
        }

        String body = sb.toString();
        
        return body;
    }

That method gets the ball rolling by instantiating a StringBuilder object. That's necessary in case there's more than one HTML MessagePart object. 

The first if check you see is just a null trap. I hate NullPointerException.

Next, the for loop steps through each MessagePart object. Then, it checks the MIME type of each object to see if it's HTML. 

If so, the code grabs the data property from the MessagePartBody object that's part of the MessagePart object (whew). 

That data property is a String but it's Base64 encoded. So the method decodes it.

Then, the code appends that decoded String to the StringBuilder object.

Once the loop has run its course, the StringBuilder object gets converted to a String and returned to the calling method.

And here's the getPlainText() method:

    private String getPlainText(Message message) {
        StringBuilder sb = new StringBuilder();
        
        if (message.getPayload() != null && message.getPayload().getParts() != null) {
            for (MessagePart msgPart : message.getPayload().getParts()) {
                if (msgPart.getMimeType().contains("text/plain"))
                    sb.append((new String(Base64.getUrlDecoder().decode(msgPart.getBody().getData()))));
            }           
        }

        String body = sb.toString();
        
        return body;
    }

It's really just the same thing as the getHtmlBody() method except that it's checking for a text/plain MIME type instead of text/html.

That, of course, means there's an opportunity for refactoring. I'll take a look at that later.

And here's the getData() method:

    private String getData(Message message) {
        StringBuilder sb = new StringBuilder();
        
        if (message.getPayload() != null && message.getPayload().getBody() != null && message.getPayload().getBody().getData() != null) {
            sb.append((new String(Base64.getUrlDecoder().decode(message.getPayload().getBody().getData()))));
        }

        String body = sb.toString();
        
        return body;
    }

That's the method that gets used when the Message object doesn't include any MessagePart objects. You can usually see that message in action if you send a plain text email from another email client to your Gmail address.

Wrapping It Up

Now you know how to translate a Message object into a custom Email object. That's going to come in handy when you want to send the client an array of Email messages from his or her inbox.

But that will happen in a future guide.

For now, you're right where you need to be.

Have fun!

Photo by Mikael Blomkvist from Pexels