GreenMail not returning BCC recipient addresses

1k Views Asked by At

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

There are 2 best solutions below

1
Stephen M -on strike- On BEST ANSWER

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("[email protected]", "");
MailFolder inbox = mailServer.getManagers().getImapHostManager().getInbox(user);
for(StoredMessage message : inbox.getMessages()) {
   // Verify the messages
}
0
Nos On

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
  ) {}

}