Editing a javax.mail.Message Body

Well, there are plenty of resources out there on creating a javax.mail.Message through JavaMail (just spend a few minutes Googling for it), but heaven help you if you need to do anything with that Message once it’s created; the resources are thin for editing a Message.

Luckily, we at engfer(s) had a task to attach a system message to the bottom of every email (html & text) that leaves the system. We thought it would be easy; however, it was not. It took hours of println()’s to figure out how to just get to the message body. We will share some information we gathered with you so it may help alleviate the pain that we had to go through.

The Example Problem

  • Task: Append the String “<div style=’font-size: 8pt; font-family: sans-serif; clear: both;’ align=’center’><i>:: Sent From the System ::</i></div>” to every email that leaves the system
  • Background: There exists a place where all emails in the system get filtered through a certain location. It will be our job to intercept these messages, attach the string, and send them on their merry way

The Quirky Structure of the Java Mail Message

If you get bored and decide to parse through the javdocs for the javax.mail.Message, you will see plenty of getter’s and setter’s. These are great if you want to view or change the Date, Subject, From, Folder, Recipients and other useless information for our example. Hmm, but nothing in there about getting the text or body directly.

Well, there is a getContent() method from the extended class Part that returns an Object, so maybe that is probably where the body lies (and it does). Unfortunately, an Object is returned, so we are going to have to use the ugly instanceof operator to test what the heck it is. At this point, all we know is shown by Figure 1; we have a Message with some Object called ‘content’.

Figure 1

Consequently, we played around with some println()’s and the instanceof operator to figure out what the heck the Object is. Well, we came across a few situations of what that Object might be/contain.

First, let’s see what code scaffold we came up with to test this (we are just trying to grab the message content):

// There exists some parameter pMessage
// which is a javax.mail.Message
try {
  // Try and grab the unknown content
  Object content = pMessage.getContent();

}catch ( MessagingException e ) {
  e.printStackTrace();
} catch ( IOException e ) {
  e.printStackTrace();
}

1) The Content is an Instance of a String

As you can see in Figure 2, we found that the content was nothing more than a simple String that contained the body of the message.

Figure 2

Wow, that was easy! We can just take the body, add our html-stripped version of our String, set the new content (which sets the MIME type to “text/plain”), and call it a day (lines 08 – 14):

// There exists some parameter pMessage
// which is a javax.mail.Message
try {
  // Try and grab the unknown content
  Object content = pMessage.getContent();

  // Grab the body content text
  if ( content instanceof String ) {
    String body = (String) content;
    // Add our custom message
    body += stripTags( mesgStr );
    // Set the new content
    pMessage.setContent( body );
  }
}catch ( MessagingException e ) {
  e.printStackTrace();
} catch ( IOException e ) {
  e.printStackTrace();
}

Doh! The only problem is that having the message content as String is a super trivial case.

2) The Content is an Instance of a Multipart that Contains Text and HTML Versions of the Message

Much of today’s email is sent in HTML; however, some people/clients still don’t want to see (or can’t see) HTML email, so a text version of the email is usually included as well.

Figure 3

Figure 3 shows exactly how a text and HTML version of an email is packaged.

The Content is an instanceof Multipart which contains a series of BodyPart‘s; heck, it can even be an instance of a MimeMultipart with MimeBodyPart‘s (which it happens to be in our case).

Since MimeBodyPart (and BodyPart) extends Part as well, they too have the getContent() method; however, it is at this point that we should call the isMimeType( String mimeType ) method to see if it’s “text/plain” or “text/html”.

Let’s put our findings into code. The one thing we’ll be sure to do is make parsing of a Multipart a method of it’s own (line 27 below); we shall see why in a few moments.

public void editMessage( Message pMessage ) {
  // There exists some parameter pMessage
  // which is a javax.mail.Message
  try {
    // Try and grab the unknown content
    Object content = pMessage.getContent();

    // Grab the body content text
    if ( content instanceof String ) {
      String body = (String) content;
      // Add our custom message
      body += stripTags( mesgStr );
      // Set the new content
      pMessage.setContent( body );
    } else if ( content instanceof Multipart ) {
      // Make sure to cast to it's Multipart derivative
      parseMultipart( (Multipart) content );
    }
  }catch ( MessagingException e ) {
    e.printStackTrace();
  } catch ( IOException e ) {
    e.printStackTrace();
  }
}

// Parse the Multipart to find the body
public void parseMultipart( Multipart mPart ) {
  // Loop through all of the BodyPart's
  for ( int i = 0; i < mPart.getCount(); i++ ) {
    // Grab the body part
    BodyPart bp = mPart.getBodyPart( i );
    // Grab the disposition for attachments
    String disposition = mPart.getDisposition();

    // It's not an attachment
    if ( disposition == null && bp instanceof MimeBodyPart ){
      MimeBodyPart mbp = (MimeBodyPart) bp;

      // Time to grab and edit the body
      if ( mbp.isMimeType( "text/plain" ) {
        // Grab the body containing the text version
        String body = (String) mbp.getContent();
        // Add our custom message
        body += stripTags( mesgStr );

        // Reset the content
        mbp.setContent( body, "text/plain" );
      } else if ( mbp.isMimeType( "text/html" ) {
        // Grab the body containing the HTML version
        String body = (String) mbp.getContent();
        // Add our custom message to the HTML before
        // the closing </body>
        body = addStrToHtmlBody( mesgStr, body );

        // Reset the content
        mbp.setContent( body, "text/html" );
      }
    }
  }
}

Notice how on lines 40 & 48 how we made sure to check the MimeType‘s; also notice how we checked for a disposition on lines 33 & 36 to see if it is an attachment. We aren’t going to show you the addStrToHtmlBody( mesgStr, body ) implementation because we think you can do it easily enough with some regular expressions or body.replace()‘s.

The code at this point is almost finished; there is just one more major instance that will break our code.

3) The Content is an Instance of a Multipart that Contains Attachments and a “Sub”-Multipart that Contains the Text and HTML Versions of the Message

For some unworldly reason, Mr. eMail man decided that instead of adding attachments as BodyPart’s to the current Multipart, he decided to bury the message text Multipart as a sub-element to another Multipart.

What?! Yes, you read that right. It’s a Multipart containing two or more BodyParts: 1) A BodyPart(s) that contain(s) the attchment(s) 2) A BodyPart whose content is a Multipart which has the two MimeBodyPart’s containing the text & HTML versions of the message.

What in the heck did that mean? Just stare at Figure 4 for a bit and read the last paragraph again.

Figure 4

It’s the most screwy thing we can think of right now. We’re sure there’s a valid reason for it; if you know what it is, feel free to post a comment about it.

Now, let’s go back and fix our code to handle this haphazard situation.

public void editMessage( Message pMessage ) {
  // There exists some parameter pMessage
  // which is a javax.mail.Message
  try {
    // Try and grab the unknown content
    Object content = pMessage.getContent();

    // Grab the body content text
    if ( content instanceof String ) {
      String body = (String) content;
      // Add our custom message
      body += stripTags( mesgStr );
      // Set the new content
      pMessage.setContent( body );
    } else if ( content instanceof Multipart ) {
      // Make sure to cast to it's Multipart derivative
      parseMultipart( (Multipart) content );
    }
  }catch ( MessagingException e ) {
    e.printStackTrace();
  } catch ( IOException e ) {
    e.printStackTrace();
  }
}

// Parse the Multipart to find the body
public void parseMultipart( Multipart mPart ) {
  // Loop through all of the BodyPart's
  for ( int i = 0; i < mPart.getCount(); i++ ) {
    // Grab the body part
    BodyPart bp = mPart.getBodyPart( i );
    // Grab the disposition for attachments
    String disposition = mPart.getDisposition();

    // It's not an attachment
    if ( disposition == null && bp instanceof MimeBodyPart ){
      MimeBodyPart mbp = (MimeBodyPart) bp;

      // Check to see if we're in the screwy situation where
      // the message text is buried in another Multipart
      if ( mbp.getContent() instanceOf Multipart ) {
        // Use recursion to parse the sub-Multipart
        parseMultipart( (Multipart) mbp.getContent() );
      } else {
        // Time to grab and edit the body
        if ( mbp.isMimeType( "text/plain" ) {
          // Grab the body containing the text version
          String body = (String) mbp.getContent();
          // Add our custom message
          body += stripTags( mesgStr );

          // Reset the content
          mbp.setContent( body, "text/plain" );
        } else if ( mbp.isMimeType( "text/html" ) {
          // Grab the body containing the HTML version
          String body = (String) mbp.getContent();
          // Add our custom message to the HTML before
          // the closing </body>
          body = addStrToHtmlBody( mesgStr, body );

          // Reset the content
          mbp.setContent( body, "text/html" );
        }
      }
    }
  }
}

Check out lines 41 – 44. Ahhh! Now we see why we made parseMultipart a separate function, so we could use recursion to tackle the problem for us!

9 thoughts on “Editing a javax.mail.Message Body

  1. Great work!

    I was working on the same issue and couldn’t find anything on it until I got to this page which shows it perfectly.

  2. I am working on this problem too.
    I write my own code with the same idea. but it does not work – setContent do nothing!
    I decide that make some mistake and try to use your code, but it does not compile: there is not method setContent wich take one parametr of type String

    String body = (String) content;
    // Add our custom message
    body = data;
    // Set the new content
    –>> msg.setContent( body );

  3. If you look at the two setContent methods on javax.mail.Message (which comes from the implemented interface javax.mail.Part), you will see that the one of the methods takes a single parameter, javax.mail.Multipart, and the other method takes an Object and and a String.

    The correct method you want to use is the two parameter version; the second parameter is the MIME type (“text/html”, “text/plain”, etc). My code may well be incorrect now that I think about it, it was adapted from something I have made, so I didn’t test if it compiled.

    msg.setContent( body, "text/html" );
    
  4. Hello!

    Code in post contain right idea but has some errors. Main error is that changes not saved.
    This code works properly (it edits only “text/plain”, but for “text/html” changes are minimal):

    public void updateTextInMessage (Message msg)
     throws MessagingException, IOException
    {
    Object content = msg.getContent ();
    if (content instanceof String)
      updateTextInMessage ((Part) msg);
    else {
      updateTextInMessage ((Multipart) content);
      msg.setContent ((Multipart) content);
    }
    msg.saveChanges ();
    }
    
    private void updateTextInMessage (Multipart multipart)
     throws MessagingException, IOException
    {
     int partsCount = multipart.getCount ();
     for (int i = 0; i < partsCount;   i) {
       BodyPart bodyPart = multipart.getBodyPart (i);
       String disposition = bodyPart.getDisposition ();
       if (disposition == null && bodyPart instanceof MimeBodyPart) {
         MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart;
         Object content = mimeBodyPart.getContent ();
         if (content instanceof Multipart) {
           updateTextInMessage ((Multipart) content);
           mimeBodyPart.setContent ((Multipart) content);
         }
         else if (mimeBodyPart.isMimeType ("text/plain"))
           updateTextInMessage ((Part) bodyPart);
       }
     }
    }
    
    private void updateTextInMessage (Part textPart)
     throws MessagingException, IOException
    {
     String body = (String) textPart.getContent ();
     body  = "\r\n\r\nAdded text.\r\n";
     textPart.setContent (body, "text/plain");
    }
  5. Yesterday, while I was at work, my cousin stole my Apple iPad and tested to see if it can survive 1:30 foot drop, just so she can be a youtube sensation. My apple ipad is now destroyed and she has 83 views. I know this is totally off topic but I had to share it with someone! |

  6. Awsome explanation. I did see similar stuff on other sites but was confused why so many checks were required. You cleared my doubts!

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>