diff --git a/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt b/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt index 213ae0af55..72be4f83aa 100644 --- a/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt +++ b/projects/justice-email-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/IntegrationTest.kt @@ -3,12 +3,10 @@ package uk.gov.justice.digital.hmpps import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.mockito.Mockito.atLeastOnce -import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.eq -import org.mockito.kotlin.verify +import org.mockito.kotlin.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.mock.mockito.MockBean @@ -20,6 +18,7 @@ import uk.gov.justice.digital.hmpps.message.Notification import uk.gov.justice.digital.hmpps.messaging.EmailMessage import uk.gov.justice.digital.hmpps.messaging.Handler import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader.get +import uk.gov.justice.digital.hmpps.service.MailboxService import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived import uk.gov.justice.digital.hmpps.telemetry.TelemetryService @@ -34,6 +33,9 @@ internal class IntegrationTest { @MockBean lateinit var telemetryService: TelemetryService + @MockBean + lateinit var mailBoxService: MailboxService + @Test fun `contact is created`() { val notification = Notification(get("successful-message")) @@ -113,8 +115,8 @@ internal class IntegrationTest { @Test fun `error when missing crn`() { val notification = Notification(get("no-crn")) - val exception = assertThrows { handler.handle(notification) } - assertThat(exception.message, equalTo("No CRN in message subject")) + assertDoesNotThrow { handler.handle(notification) } + verify(mailBoxService).onUnableToCreateContactFromEmail(any()) } @Test diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt index 758d5e2db6..021599e49a 100644 --- a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/Handler.kt @@ -5,6 +5,7 @@ import org.openfolder.kotlinasyncapi.annotation.Schema import org.openfolder.kotlinasyncapi.annotation.channel.Channel import org.openfolder.kotlinasyncapi.annotation.channel.Message import org.openfolder.kotlinasyncapi.annotation.channel.Publish +import org.springframework.context.ApplicationEventPublisher import org.springframework.ldap.NameNotFoundException import org.springframework.ldap.core.AttributesMapper import org.springframework.ldap.core.LdapTemplate @@ -19,6 +20,7 @@ import uk.gov.justice.digital.hmpps.datetime.DeliusDateFormatter import uk.gov.justice.digital.hmpps.entity.* import uk.gov.justice.digital.hmpps.entity.ContactType.Code.EMAIL import uk.gov.justice.digital.hmpps.entity.Person.Companion.CRN_REGEX +import uk.gov.justice.digital.hmpps.exception.IgnorableMessageException import uk.gov.justice.digital.hmpps.message.Notification import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived import uk.gov.justice.digital.hmpps.telemetry.TelemetryService @@ -37,56 +39,66 @@ class Handler( private val personManagerRepository: PersonManagerRepository, private val staffRepository: StaffRepository, private val ldapTemplate: LdapTemplate, + private val eventPublisher: ApplicationEventPublisher, ) : NotificationHandler, AuditableService(auditedInteractionService) { @Publish(messages = [Message(title = "email-message", payload = Schema(EmailMessage::class))]) - override fun handle(notification: Notification) = audit(ADD_CONTACT) { audit -> - telemetryService.notificationReceived(notification) - val message = notification.message + override fun handle(notification: Notification) = try { + audit(ADD_CONTACT) { audit -> + telemetryService.notificationReceived(notification) + val message = notification.message - val crn = message.extractCrn() - val emailAddress = - message.fromEmailAddress.takeIf { it.endsWith("@justice.gov.uk") || it.endsWith("@digital.justice.gov.uk") } - ?: throw IllegalArgumentException("Email address does not end with @justice.gov.uk or @digital.justice.gov.uk") - val person = personRepository.getByCrn(crn) - val manager = personManagerRepository.getManager(person.id) - val staffId = findStaffIdForEmailAddress(emailAddress) ?: manager.staffId - val fullNotes = """ + val crn = message.extractCrn() + val emailAddress = + message.fromEmailAddress.takeIf { it.endsWith("@justice.gov.uk") || it.endsWith("@digital.justice.gov.uk") } + ?: throw IllegalArgumentException("Email address does not end with @justice.gov.uk or @digital.justice.gov.uk") + val person = personRepository.getByCrn(crn) + val manager = personManagerRepository.getManager(person.id) + val staffId = findStaffIdForEmailAddress(emailAddress) ?: manager.staffId + val fullNotes = """ |This contact was created automatically from a forwarded email sent by ${message.fromEmailAddress} ${message.onAt}. |Subject: ${message.subject} | |${htmlToMarkdownConverter.convert(message.bodyContent)} """.trimMargin() - val contact = contactRepository.save( - Contact( - personId = person.id, - externalReference = "urn:uk:gov:hmpps:justice-email:${message.id}", - type = contactTypeRepository.getByCode(EMAIL), - date = message.receivedDateTime, - startTime = message.receivedDateTime, - description = "Email - ${message.subject.replace(CRN_REGEX.toRegex(), "").trim()}".truncated(), - notes = fullNotes, - staffId = staffId, - teamId = manager.teamId, - providerId = manager.providerId, + val contact = contactRepository.save( + Contact( + personId = person.id, + externalReference = "urn:uk:gov:hmpps:justice-email:${message.id}", + type = contactTypeRepository.getByCode(EMAIL), + date = message.receivedDateTime, + startTime = message.receivedDateTime, + description = "Email - ${message.subject.replace(CRN_REGEX.toRegex(), "").trim()}".truncated(), + notes = fullNotes, + staffId = staffId, + teamId = manager.teamId, + providerId = manager.providerId, + ) ) - ) - audit["contactId"] = contact.id + audit["contactId"] = contact.id - telemetryService.trackEvent( - "CreatedContact", mapOf( - "crn" to crn, - "staffId" to staffId.toString(), - "contactId" to contact.id.toString(), - "messageId" to message.id, + telemetryService.trackEvent( + "CreatedContact", mapOf( + "crn" to crn, + "staffId" to staffId.toString(), + "contactId" to contact.id.toString(), + "messageId" to message.id, + ) ) - ) + } + } catch (_: IgnorableMessageException) { } private fun EmailMessage.extractCrn(): String { val crns = CRN_REGEX.toRegex().findAll(subject).map { it.value }.distinct() return when (crns.count()) { 1 -> crns.single().uppercase() - 0 -> throw IllegalArgumentException("No CRN in message subject") + 0 -> { + eventPublisher.publishEvent( + UnableToCreateContactFromEmail(this, "Unable to parse CRN from message subject") + ) + throw IgnorableMessageException("No CRN in message subject") + } + else -> throw IllegalArgumentException("Multiple CRNs in message subject") } } diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/UnableToCreateContactFromEmail.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/UnableToCreateContactFromEmail.kt new file mode 100644 index 0000000000..effc9226d6 --- /dev/null +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/messaging/UnableToCreateContactFromEmail.kt @@ -0,0 +1,3 @@ +package uk.gov.justice.digital.hmpps.messaging + +data class UnableToCreateContactFromEmail(val email: EmailMessage, val reason: String) \ No newline at end of file diff --git a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt index 5010ce1716..cc9e114b1d 100644 --- a/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt +++ b/projects/justice-email-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/MailboxService.kt @@ -1,14 +1,20 @@ package uk.gov.justice.digital.hmpps.service +import com.microsoft.graph.models.EmailAddress +import com.microsoft.graph.models.ItemBody import com.microsoft.graph.models.Message +import com.microsoft.graph.models.Recipient import com.microsoft.graph.serviceclient.GraphServiceClient +import com.microsoft.graph.users.item.sendmail.SendMailPostRequestBody import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.instrumentation.annotations.WithSpan import org.springframework.beans.factory.annotation.Value +import org.springframework.context.event.EventListener import org.springframework.stereotype.Service import uk.gov.justice.digital.hmpps.message.MessageAttributes import uk.gov.justice.digital.hmpps.message.Notification import uk.gov.justice.digital.hmpps.messaging.EmailMessage +import uk.gov.justice.digital.hmpps.messaging.UnableToCreateContactFromEmail import uk.gov.justice.digital.hmpps.publisher.NotificationPublisher import uk.gov.justice.digital.hmpps.telemetry.TelemetryService @@ -31,6 +37,21 @@ class MailboxService( } } + @EventListener(UnableToCreateContactFromEmail::class) + fun onUnableToCreateContactFromEmail(event: UnableToCreateContactFromEmail) { + val toEmailAddress = EmailAddress().apply { address = event.email.fromEmailAddress } + val message = Message().apply { + subject = "Unable to create contact from email" + body = ItemBody().apply { content = "Reason for the contact not being created: ${event.reason}" } + toRecipients = listOf(Recipient().apply { emailAddress = toEmailAddress }) + } + graphServiceClient.me().sendMail().post(SendMailPostRequestBody().apply { setMessage(message) }) + telemetryService.trackEvent( + "UnableToCreateContactFromEmail", + mapOf("emailId" to event.email.id, "reason" to event.reason) + ) + } + private fun getUnreadMessages() = graphServiceClient .users() .byUserId(emailAddress)