Skip to content

Commit

Permalink
Implement and test email.SesMailer.Bounce
Browse files Browse the repository at this point in the history
This required adding the new messageId parameter to the Bouncer.Bounce
interface method and updating handler/mailto{,_test}.go accordingly.

I essentially copied the implementation and test over from
mbland/ses-forwarder. I'll have to extract a common package one day.

After this, I'll implement IsSuppressed and Suppress. Then onto
implementing either the CLI for sending messages or the ProdAgent. Down
to two major pieces before MVP!
  • Loading branch information
mbland committed May 3, 2023
1 parent 8d1f6b9 commit 0d8a0fa
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 11 deletions.
43 changes: 34 additions & 9 deletions email/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ type Mailer interface {
type Bouncer interface {
Bounce(
ctx context.Context,
emailDomain string,
emailDomain,
messageId string,
recipients []string,
timestamp time.Time,
) (string, error)
Expand Down Expand Up @@ -69,19 +70,43 @@ func (mailer *SesMailer) Send(
return
}

// https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda-example-functions.html
func (mailer *SesMailer) Bounce(
ctx context.Context,
emailDomain string,
emailDomain,
messageId string,
recipients []string,
timestamp time.Time,
) (bounceMessageId string, err error) {
// https://docs.aws.amazon.com/ses/latest/dg/receiving-email-action-lambda-example-functions.html
// https://docs.aws.amazon.com/sdk-for-go/api/service/ses/#SES.SendBounce
// https://docs.aws.amazon.com/ses/latest/APIReference/API_SendBounce.html
// https://docs.aws.amazon.com/ses/latest/APIReference/API_MessageDsn.html
// https://docs.aws.amazon.com/sdk-for-go/api/service/ses/#MessageDsn
// https://docs.aws.amazon.com/sdk-for-go/api/service/ses/sesiface/
bounceMessageId = "fake bounce message ID"
sender := "mailer-daemon@" + emailDomain
recipientInfo := make([]types.BouncedRecipientInfo, len(recipients))
reportingMta := "dns; " + emailDomain
arrivalDate := timestamp.Truncate(time.Second)
explanation := "Unauthenticated email is not accepted due to " +
"the sending domain's DMARC policy."

for i, recipient := range recipients {
recipientInfo[i].Recipient = &recipient
recipientInfo[i].BounceType = types.BounceTypeContentRejected
}

input := &ses.SendBounceInput{
BounceSender: &sender,
OriginalMessageId: &messageId,
MessageDsn: &types.MessageDsn{
ReportingMta: &reportingMta,
ArrivalDate: &arrivalDate,
},
Explanation: &explanation,
BouncedRecipientInfoList: recipientInfo,
}
var output *ses.SendBounceOutput

if output, err = mailer.Client.SendBounce(ctx, input); err != nil {
err = fmt.Errorf("sending bounce failed: %s", err)
} else {
bounceMessageId = *output.MessageId
}
return
}

Expand Down
54 changes: 54 additions & 0 deletions email/mailer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"context"
"errors"
"testing"
"time"

"github.com/aws/aws-sdk-go-v2/service/ses"
"github.com/aws/aws-sdk-go-v2/service/ses/types"
"gotest.tools/assert"
)

Expand Down Expand Up @@ -56,7 +58,9 @@ func TestSend(t *testing.T) {

assert.NilError(t, err)
assert.Equal(t, testMsgId, msgId)

input := testSes.rawEmailInput
assert.Assert(t, input != nil)
assert.DeepEqual(t, []string{recipient}, input.Destinations)
assert.Equal(t, mailer.ConfigSet, *input.ConfigurationSetName)
assert.DeepEqual(t, testMsg, input.RawMessage.Data)
Expand All @@ -72,3 +76,53 @@ func TestSend(t *testing.T) {
assert.Error(t, err, "send failed: SendRawEmail error")
})
}

func TestBounce(t *testing.T) {
setup := func() (*TestSes, *SesMailer, context.Context) {
testSes := &TestSes{
bounceInput: &ses.SendBounceInput{},
bounceOutput: &ses.SendBounceOutput{},
}
mailer := &SesMailer{Client: testSes}
return testSes, mailer, context.Background()
}

emailDomain := "foo.com"
messageId := "deadbeef"
recipients := []string{"plugh@foo.com"}
timestamp, _ := time.Parse(time.RFC1123Z, "Fri, 18 Sep 1970 12:45:00 +0000")

t.Run("Succeeds", func(t *testing.T) {
testSes, mailer, ctx := setup()
testBouncedMessageId := "0123456789"
testSes.bounceOutput.MessageId = &testBouncedMessageId

bouncedId, err := mailer.Bounce(
ctx, emailDomain, messageId, recipients, timestamp,
)

assert.NilError(t, err)
assert.Equal(t, testBouncedMessageId, bouncedId)

input := testSes.bounceInput
assert.Assert(t, input != nil)
assert.Equal(t, len(recipients), len(input.BouncedRecipientInfoList))
bouncedRecipient := input.BouncedRecipientInfoList[0]
assert.Equal(t, recipients[0], *bouncedRecipient.Recipient)
assert.Equal(
t, types.BounceTypeContentRejected, bouncedRecipient.BounceType,
)
})

t.Run("ReturnsErrorIfSendBounceFails", func(t *testing.T) {
testSes, mailer, ctx := setup()
testSes.bounceErr = errors.New("SendBounce error")

bouncedId, err := mailer.Bounce(
ctx, emailDomain, messageId, recipients, timestamp,
)

assert.Equal(t, "", bouncedId)
assert.Error(t, err, "sending bounce failed: SendBounce error")
})
}
5 changes: 4 additions & 1 deletion handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ var testRedirects = RedirectPaths{

type testBouncer struct {
EmailDomain string
MessageId string
Recipients []string
Timestamp time.Time
ReturnMessageId string
Expand All @@ -98,11 +99,13 @@ type testBouncer struct {

func (b *testBouncer) Bounce(
ctx context.Context,
emailDomain string,
emailDomain,
messageId string,
recipients []string,
timestamp time.Time,
) (string, error) {
b.EmailDomain = emailDomain
b.MessageId = messageId
b.Recipients = recipients
b.Timestamp = timestamp
if b.Error != nil {
Expand Down
2 changes: 1 addition & 1 deletion handler/mailto.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (h *mailtoHandler) bounceIfDmarcFails(
) (bounceMessageId string, err error) {
if ev.DmarcVerdict == "FAIL" && ev.DmarcPolicy == "REJECT" {
bounceMessageId, err = h.Bouncer.Bounce(
ctx, h.EmailDomain, ev.Recipients, ev.Timestamp,
ctx, h.EmailDomain, ev.MessageId, ev.Recipients, ev.Timestamp,
)
}
return
Expand Down
1 change: 1 addition & 0 deletions handler/mailto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func TestBounceIfDmarcFails(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t, "0x123456789", bounceMessageId)
assert.Equal(t, testEmailDomain, f.bouncer.EmailDomain)
assert.Equal(t, "deadbeef", f.bouncer.MessageId)
assert.DeepEqual(t, f.event.Recipients, f.bouncer.Recipients)
assert.Equal(t, f.event.Timestamp, f.bouncer.Timestamp)
})
Expand Down

0 comments on commit 0d8a0fa

Please sign in to comment.