3

I'm using GreenMail in a test to verify that our application is sending email correctly.

I start GreenMail with:

GreenMail greenMailServer = new GreenMail(new ServerSetup(port, "localhost", ServerSetup.PROTOCOL_SMTP));
greenMailServer.start();

I have a separate process that sends the email to the GreenMail SMTP server (this is part of an integration test), so my test code waits for the email with:

long endTime = System.currentTimeMillis() + timeout;
// Get the fake mail server and listen for messages
GreenMail mailServer = ITester.getFakeEMail();
while(System.currentTimeMillis() < endTime)  {
     boolean timedOut = !mailServer.waitForIncomingEmail(endTime - System.currentTimeMillis(), 1);
     if(timedOut) {
          throw new TestFailure(lineNumber, "Timed out waiting for email To: '"+to+"' Subject: '"+subject+"' Body: '"+body+"' .");
     }
     for(MimeMessage message : mailServer.getReceivedMessages()) {
          try {
              StringBuilder errors = new StringBuilder();
              // Check who the message is going to
              //Address[] allRecipients = message.getRecipients(Message.RecipientType.BCC);
              Address[] allRecipients = message.getAllRecipients();

I've tried both the commented out request for Recipients and the getAllRecipients, but I always receive null. I've verified that my application is sending the email to one address in the BCC field.

Any idea why I'm not seeing the recipient email address?

2 Answers2

3

I found the answer on this blog:

https://developer.vz.net/2011/11/08/unit-testing-java-mail-code/

The short version is to use a user and get the message from his inbox instead of getting the message from the server as a whole. Then you know it came to that email address.

GreenMailUser user = mailServer.setUser("junk@somewhere.com", "");
MailFolder inbox = mailServer.getManagers().getImapHostManager().getInbox(user);
for(StoredMessage message : inbox.getMessages()) {
   // Verify the messages
}
  • With this method I retrieve the mail as BCC recipient, but the BCC field of the receiver is still null. But maybe it has to be like that? Because the BCC receiver is so secret? – Codev Feb 07 '20 at 08:26
0

Because Greenmail won't tell us who got Bcc'd, we have to determine it ourselves by the process of elimination.

For the given email

  • Find out the email addresses that have received the email (based on message id).
  • Remove the toRecipients from this list.
  • Remove the ccRecipients from this list.
import com.icegreen.greenmail.spring.GreenMailBean;
import com.icegreen.greenmail.store.FolderException;
import com.icegreen.greenmail.store.MailFolder;
import com.icegreen.greenmail.store.StoredMessage;
import com.icegreen.greenmail.user.GreenMailUser;
import jakarta.mail.BodyPart;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class MailExtractor {

  private final GreenMailBean greenMailBean;

  public MailExtractor(GreenMailBean greenMailBean) {
    this.greenMailBean = greenMailBean;
  }

  public Email getEmailForFirstSentMessage() {
    MimeMessage mimeMessage = greenMailBean.getReceivedMessages()[0];
    Email email = MailExtractor.extractEmail(mimeMessage, getUserMailFolderList());
    System.out.printf("From %s%n", email.from());
    System.out.printf("To %s%n", email.toRecipients());
    System.out.printf("Cc %s%n", email.ccRecipients());
    System.out.printf("Bcc %s%n", email.bccRecipients());
    System.out.printf("Body %s%n", email.body());
    System.out.printf("Message ID %s%n", email.messageId());
    return email;
  }

  private Map<GreenMailUser, MailFolder> getUserMailFolderList() {
    Collection<GreenMailUser> greenMailUsers = greenMailBean.getGreenMail().getUserManager().listUser();
    Map<GreenMailUser, MailFolder> userMailFolder = new HashMap<>();
    for (GreenMailUser greenMailUser : greenMailUsers) {
      try {
        userMailFolder.put(greenMailUser, greenMailBean.getGreenMail().getManagers().getImapHostManager().getInbox(greenMailUser));
      } catch (FolderException e) {
        throw new RuntimeException(e);
      }
    }
    return userMailFolder;
  }

  private static Email extractEmail(MimeMessage mimeMessage, Map<GreenMailUser, MailFolder> userMailFolderList) {
    return new Email(extractFrom(mimeMessage),
                     extractTo(mimeMessage),
                     extractCc(mimeMessage),
                     extractBcc(mimeMessage, userMailFolderList),
                     extractSubject(mimeMessage),
                     extractBody(mimeMessage),
                     extractMessageId(mimeMessage));
  }

  private static String extractFrom(MimeMessage mimeMessage) {
    return getValueForField(mimeMessage, "From");
  }

  private static List<String> extractTo(MimeMessage mimeMessage) {
    return getValuesForField(mimeMessage, "To");
  }

  private static List<String> extractCc(MimeMessage mimeMessage) {
    return getValuesForField(mimeMessage, "Cc");
  }

  /* Because Greenmail won't tell us who got Bcc'd, we have to determine it ourselves by the process of elimination.
   *
   * For the given email
   * -> Find out the email addresses that have received the email (based on message id)
   * -> Remove the toRecipients from this list
   * -> Remove the ccRecipients from this list
   */
  private static List<String> extractBcc(MimeMessage mimeMessage, Map<GreenMailUser, MailFolder> userMailFolder) {
    List<String> bccList = new ArrayList<>();
    for (Entry<GreenMailUser, MailFolder> mailFolder : userMailFolder.entrySet()) {
      mailFolder.getValue().getMessages().stream()
                .filter(storedMessage -> messageIdsMatch(storedMessage, extractMessageId(mimeMessage)))
                .findFirst()
                .map(it -> bccList.add(mailFolder.getKey().getEmail()));
    }

    bccList.removeAll(extractTo(mimeMessage));
    bccList.removeAll(extractCc(mimeMessage));
    return bccList;
  }

  private static boolean messageIdsMatch(StoredMessage storedMessage, String messageId) {
    try {
      return storedMessage.getMimeMessage().getMessageID().equals(messageId);
    } catch (MessagingException e) {
      throw new RuntimeException(e);
    }
  }

  private static String extractSubject(MimeMessage mimeMessage) {
    return getValueForField(mimeMessage, "Subject");
  }

  private static String extractMessageId(MimeMessage mimeMessage) {
    return getValueForField(mimeMessage, "Message-ID");
  }

  private static String extractBody(MimeMessage mimeMessage) {
    try {
      if (mimeMessage.getContent() instanceof MimeMultipart mimeMultipart) {
        return findHtmlBody(mimeMultipart).orElseThrow(() -> new IllegalStateException("Could not find html-body"));
      }
      throw new IllegalArgumentException("Unexpected e-mail content of type " + mimeMessage.getContent().getClass());
    } catch (IOException | MessagingException e) {
      throw new IllegalArgumentException(e);
    }
  }

  private static Optional<String> findHtmlBody(MimeMultipart multipart) {
    try {
      for (int i = 0; i < multipart.getCount(); i++) {
        BodyPart bodyPart = multipart.getBodyPart(i);
        if ("text/html;charset=UTF-8".equals(bodyPart.getContentType())) {
          return Optional.of(((String) bodyPart.getContent()).replace("\r", ""));
        }
        if (bodyPart.getContent() instanceof MimeMultipart subMultipart) {
          return findHtmlBody(subMultipart);
        }
      }
    } catch (MessagingException | IOException e) {
      throw new RuntimeException(e);
    }
    return Optional.empty();
  }

  private static List<String> getValuesForField(MimeMessage mimeMessage, String headerName) {
    String value = getValueForField(mimeMessage, headerName);
    return value != null ? Arrays.stream(value.split(", ")).toList() : List.of();
  }

  private static String getValueForField(MimeMessage mimeMessage, String headerName) {
    try {
      return Optional.ofNullable(mimeMessage.getHeader(headerName)).map(content -> content[0]).orElse(null);
    } catch (MessagingException e) {
      throw new RuntimeException(e);
    }
  }

  private record Email(String from,
                       Collection<String> toRecipients,
                       Collection<String> ccRecipients,
                       Collection<String> bccRecipients,
                       String subject,
                       String body,
                       String messageId
  ) {}

}

Nos
  • 1,024
  • 1
  • 8
  • 14