Skip to content

Commit

Permalink
Call email service 184899355 (#204)
Browse files Browse the repository at this point in the history
* Add mailgun dependency
* Add email clients from Shiba
* Finalize sendEmail methods
* Add tests to MailgunEmailClient
* Add javadoc
* Add setSenderEmail

Co-authored-by: Bethany Seeger <bseeger@codeforamerica.org>
  • Loading branch information
coltborg and bseeger committed May 16, 2023
1 parent cee0d08 commit 7fd8a16
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ dependencies {
implementation 'com.smartystreets.api:smartystreets-java-sdk:3.13.15'
implementation 'org.apache.pdfbox:pdfbox:2.0.28'
implementation 'org.yaml:snakeyaml:2.0'
implementation 'com.mailgun:mailgun-java:1.0.7'

compileOnly 'org.projectlombok:lombok'

Expand Down
56 changes: 56 additions & 0 deletions src/main/java/formflow/library/email/EmailClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package formflow.library.email;

import java.io.File;
import java.util.List;

public interface EmailClient {

/**
* This sends an email with the least amount of information needed to be provided.
*
* @param subject The subject line of the email
* @param recipientEmail The email address that will get the email, aka the To field
* @param emailBody The plain text version of the email body
*/
void sendEmail(
String subject,
String recipientEmail,
String emailBody
);

/**
* This sends an email with the least amount of information needed to be provided, but with attachments.
*
* @param subject The subject line of the email
* @param recipientEmail The email address that will get the email, aka the To field
* @param emailBody The plain text version of the email body
* @param attachments A list of files that will be added as attachments to the email
*/
void sendEmail(
String subject,
String recipientEmail,
String emailBody,
List<File> attachments
);

/**
* This sends an email with the most customizations.
*
* @param subject The subject line of the email
* @param recipientEmail The email address that will get the email, aka the To field
* @param emailsToCC A list of emails to be added into the CC field
* @param emailsToBCC A list of emails to be added into the BCC field
* @param emailBody The plain text version of the email body
* @param attachments A list of files that will be added as attachments to the email
* @param requireTls A way to make TLS required
*/
void sendEmail(
String subject,
String recipientEmail,
List<String> emailsToCC,
List<String> emailsToBCC,
String emailBody,
List<File> attachments,
boolean requireTls
);
}
146 changes: 146 additions & 0 deletions src/main/java/formflow/library/email/MailgunEmailClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package formflow.library.email;

import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.client.MailgunClient;
import com.mailgun.model.message.Message;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.util.List;

import static java.util.Collections.emptyList;

@Component
@Slf4j
public class MailgunEmailClient implements EmailClient {

private String senderEmail;
private final String mailgunDomain;
private MailgunMessagesApi mailgunMessagesApi;

public MailgunEmailClient(@Value("${form-flow.email-client.mailgun.sender-email:}") String senderEmail,
@Value("${form-flow.email-client.mailgun.domain:}") String mailgunDomain,
@Value("${form-flow.email-client.mailgun.key:}") String mailgunKey
) {
this.senderEmail = senderEmail;
this.mailgunDomain = mailgunDomain;
this.mailgunMessagesApi = MailgunClient.config(mailgunKey)
.createApi(MailgunMessagesApi.class);
}

/**
* This sends an email with the least amount of information needed to be provided.
* Sets empty or defaults to the rest of the parameters.
*
* @param subject The subject line of the email
* @param recipientEmail The email address that will get the email, aka the To field
* @param emailBody The plain text version of the email body
*/
@Override
public void sendEmail(
String subject,
String recipientEmail,
String emailBody) {
sendEmail(
subject,
recipientEmail,
emptyList(),
emptyList(),
emailBody,
emptyList(),
false);
}

/**
* This sends an email with the least amount of information needed to be provided, but with attachments.
* Sets empty or defaults to the rest of the parameters.
*
* @param subject The subject line of the email
* @param recipientEmail The email address that will get the email, aka the To field
* @param emailBody The plain text version of the email body
* @param attachments A list of files that will be added as attachments to the email
*/
@Override
public void sendEmail(
String subject,
String recipientEmail,
String emailBody,
List<File> attachments) {
sendEmail(
subject,
recipientEmail,
emptyList(),
emptyList(),
emailBody,
attachments,
false);
}

/**
* The main method that sends to Mailgun.
* Allows all parameter customization.
*
* @param subject The subject line of the email
* @param recipientEmail The email address that will get the email, aka the To field
* @param emailsToCC A list of emails to be added into the CC field
* @param emailsToBCC A list of emails to be added into the BCC field
* @param emailBody The plain text version of the email body
* @param attachments A list of files that will be added as attachments to the email
* @param requireTls A way to make TLS required
*/
@Override
public void sendEmail(
String subject,
String recipientEmail,
List<String> emailsToCC,
List<String> emailsToBCC,
String emailBody,
List<File> attachments,
boolean requireTls) {
Message.MessageBuilder message = Message.builder()
.from(senderEmail)
.to(recipientEmail)
.subject(subject)
.text(emailBody)
.requireTls(requireTls);

if (emailsToCC != null && !emailsToCC.isEmpty()) {
message.cc(emailsToCC);
}
if (emailsToBCC != null && !emailsToBCC.isEmpty()) {
message.bcc(emailsToBCC);
}
if (attachments != null && !attachments.isEmpty()) {
message.attachment(attachments);
}
Message builtMessage = message.build();

try {
mailgunMessagesApi.sendMessage(mailgunDomain, builtMessage);
} catch (FeignException exception) {
log.error(exception.getMessage());
}
}

/**
* This setter allows us to replace mailgunMessageApi with a mock for testing.
*
* @param mailgunMessageApi The mailgunMessageApi you want to use to interface with Mailgun.
*/
public void setMailgunMessagesApi(MailgunMessagesApi mailgunMessageApi) {
this.mailgunMessagesApi = mailgunMessageApi;
}

/**
* This setter allows you to change the senderEmail.
* By default, senderEmail is defined in application.yaml.
*
* @param senderEmail The email that is used to fill the from field.
*/
public void setSenderEmail(String senderEmail) {
this.senderEmail = senderEmail;
}
}
152 changes: 152 additions & 0 deletions src/test/java/formflow/library/email/MailgunEmailClientTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package formflow.library.email;

import com.mailgun.api.v3.MailgunMessagesApi;
import com.mailgun.model.message.Message;
import formflow.library.utilities.AbstractMockMvcTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.util.Arrays;
import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

public class MailgunEmailClientTest extends AbstractMockMvcTest {

@Autowired
private MailgunEmailClient mailgunEmailClient;

private final MailgunMessagesApi mailgunMessagesApi = mock(MailgunMessagesApi.class);

@Override
@BeforeEach
public void setUp() throws Exception {
mailgunEmailClient.setMailgunMessagesApi(mailgunMessagesApi);
super.setUp();
}

@Test
public void mailgunWillSendWithMinimumInfo() throws Exception {
final ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
doReturn(null).when(mailgunMessagesApi).sendMessage(any(), captor.capture());

String expectedSubject = "sendEmail with no CC's or attachments";
String expectedRecipient = "test@example.com";
String expectedBody = "Email body";

mailgunEmailClient.sendEmail(
expectedSubject,
expectedRecipient,
expectedBody
);

final Message builtMessage = captor.getValue();
assertThat(builtMessage).isNotNull();
assertThat(builtMessage.getSubject()).isEqualTo(expectedSubject);
assertThat(builtMessage.getTo().contains(expectedRecipient)).isEqualTo(true);
assertThat(builtMessage.getText()).isEqualTo(expectedBody);
assertThat(builtMessage.getCc()).isNull();
assertThat(builtMessage.getBcc()).isNull();
assertThat(builtMessage.getAttachment()).isNull();
assertThat(builtMessage.getRequireTls()).isEqualTo("no");
}

@Test
public void mailgunWillSendWithDifferentSenderEmail() throws Exception {
final ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
doReturn(null).when(mailgunMessagesApi).sendMessage(any(), captor.capture());

String expectedSubject = "sendEmail with no CC's or attachments";
String expectedRecipient = "test@example.com";
String expectedBody = "Email body";
String expectedSenderEmail = "Another Sender <test@example.org>";

mailgunEmailClient.setSenderEmail(expectedSenderEmail);
mailgunEmailClient.sendEmail(
expectedSubject,
expectedRecipient,
expectedBody
);

final Message builtMessage = captor.getValue();
assertThat(builtMessage).isNotNull();
assertThat(builtMessage.getFrom()).isEqualTo(expectedSenderEmail);
assertThat(builtMessage.getSubject()).isEqualTo(expectedSubject);
assertThat(builtMessage.getTo().contains(expectedRecipient)).isEqualTo(true);
assertThat(builtMessage.getText()).isEqualTo(expectedBody);
assertThat(builtMessage.getCc()).isNull();
assertThat(builtMessage.getBcc()).isNull();
assertThat(builtMessage.getAttachment()).isNull();
assertThat(builtMessage.getRequireTls()).isEqualTo("no");
}


@Test
public void mailgunWillSendWithMinimumInfoAndAttachments() throws Exception {
final ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
doReturn(null).when(mailgunMessagesApi).sendMessage(any(), captor.capture());

String expectedSubject = "sendEmail with no CC's";
String expectedRecipient = "test@example.com";
String expectedBody = "Email body";
List<File> expectedAttachments = Arrays.asList(new File("src/test/resources/pdfs/blankPdf.pdf"), new File("src/test/resources/pdfs/testPdf.pdf"));

mailgunEmailClient.sendEmail(
expectedSubject,
expectedRecipient,
expectedBody,
expectedAttachments
);

final Message builtMessage = captor.getValue();
assertThat(builtMessage).isNotNull();
assertThat(builtMessage.getSubject()).isEqualTo(expectedSubject);
assertThat(builtMessage.getTo().contains(expectedRecipient)).isEqualTo(true);
assertThat(builtMessage.getText()).isEqualTo(expectedBody);
assertThat(builtMessage.getCc()).isNull();
assertThat(builtMessage.getBcc()).isNull();
assertThat(builtMessage.getAttachment().size()).isEqualTo(2);
assertThat(builtMessage.getRequireTls()).isEqualTo("no");
}

@Test
public void mailgunWillSendAllProvidedInfo() throws Exception {
final ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
doReturn(null).when(mailgunMessagesApi).sendMessage(any(), captor.capture());

String expectedSubject = "sendEmail with all parameters";
String expectedRecipient = "test@example.com";
List<String> expectedEmailsToCC = Arrays.asList("one@test.com", "two@test.com", "three@test.com");
List<String> expectedEmailsToBCC = Arrays.asList("secret-one@test.com", "secret-two@test.com");
String expectedBody = "Email body";
List<File> expectedAttachments = Arrays.asList(new File("src/test/resources/pdfs/blankPdf.pdf"));
Boolean expectedRequireTls = true;

mailgunEmailClient.sendEmail(
expectedSubject,
expectedRecipient,
expectedEmailsToCC,
expectedEmailsToBCC,
expectedBody,
expectedAttachments,
expectedRequireTls
);

final Message builtMessage = captor.getValue();
assertThat(builtMessage).isNotNull();
assertThat(builtMessage.getSubject()).isEqualTo(expectedSubject);
assertThat(builtMessage.getTo().contains(expectedRecipient)).isEqualTo(true);
assertThat(builtMessage.getText()).isEqualTo(expectedBody);
assertThat(builtMessage.getCc()).isNotNull();
assertThat(builtMessage.getCc().size()).isEqualTo(3);
assertThat(builtMessage.getBcc()).isNotNull();
assertThat(builtMessage.getBcc().size()).isEqualTo(2);
assertThat(builtMessage.getAttachment().size()).isEqualTo(1);
assertThat(builtMessage.getRequireTls()).isEqualTo("yes");
}
}
5 changes: 5 additions & 0 deletions src/test/resources/application-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ form-flow:
pdf:
path: 'src/test/resources/pdfs/'
map-file: 'pdf-map.yaml'
email-client:
mailgun:
key: testing-fake-key
domain: 'mail.forms-starter.cfa-platforms.org'
sender-email: 'Testing <test@mail.forms-starter.cfa-platforms.org>'
spring:
datasource:
url: jdbc:postgresql://localhost:5432/form-flow-test
Expand Down

0 comments on commit 7fd8a16

Please sign in to comment.