diff --git a/go.mod b/go.mod
index 4713e000e70dc236794b7cfef22b8ae8953eb5ea..97abf8a7e8ecfa30df469cb43f47287bf46b8f5b 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/MindscapeHQ/raygun4go v1.1.1
 	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
 	github.com/aws/aws-lambda-go v1.26.0
-	github.com/aws/aws-sdk-go-v2 v1.27.1
+	github.com/aws/aws-sdk-go-v2 v1.30.0
 	github.com/aws/aws-sdk-go-v2/config v1.27.10
 	github.com/aws/aws-sdk-go-v2/credentials v1.17.10
 	github.com/aws/aws-sdk-go-v2/service/apigatewaymanagementapi v1.19.9
@@ -44,14 +44,15 @@ require (
 	github.com/aws/aws-sdk-go v1.44.180 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.8 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.8 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.5 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ses v1.23.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
@@ -72,6 +73,7 @@ require (
 	github.com/mattn/go-runewidth v0.0.10 // indirect
 	github.com/olekukonko/tablewriter v0.0.5 // indirect
 	github.com/pborman/uuid v1.2.1 // indirect
+	github.com/prozz/aws-embedded-metrics-golang v1.2.0 // indirect
 	github.com/rivo/uniseg v0.1.0 // indirect
 	github.com/smartystreets/goconvey v1.7.2 // indirect
 	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
@@ -89,5 +91,7 @@ require (
 	golang.org/x/term v0.19.0 // indirect
 	google.golang.org/appengine v1.6.6 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
 	mellium.im/sasl v0.2.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 8bb29d35362e77708cc7f003da98953688d48343..82ebf050f317ffa1e5efe28a40a210022df883b7 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,8 @@ github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8
 github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
 github.com/aws/aws-sdk-go-v2 v1.27.1 h1:xypCL2owhog46iFxBKKpBcw+bPTX/RJzwNj8uSilENw=
 github.com/aws/aws-sdk-go-v2 v1.27.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
+github.com/aws/aws-sdk-go-v2 v1.30.0 h1:6qAwtzlfcTtcL8NHtbDQAqgM5s6NDipQTkPxyH/6kAA=
+github.com/aws/aws-sdk-go-v2 v1.30.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
 github.com/aws/aws-sdk-go-v2/config v1.18.8/go.mod h1:5XCmmyutmzzgkpk/6NYTjeWb6lgo9N170m1j6pQkIBs=
@@ -28,9 +30,13 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24L
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.8 h1:RnLB7p6aaFMRfyQkD6ckxR7myCC9SABIqSz4czYUUbU=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.8/go.mod h1:XH7dQJd+56wEbP1I4e4Duo+QhSMxNArE8VP7NuUOTeM=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 h1:SJ04WXGTwnHlWIODtC5kJzKbeuHt+OUNOgKg7nfnUGw=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12/go.mod h1:FkpvXhA92gb3GE9LD6Og0pHHycTxW7xGpnEh5E7Opwo=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.8 h1:jzApk2f58L9yW9q1GEab3BMMFWUkkiZhyrRUtbwUbKU=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.8/go.mod h1:WqO+FftfO3tGePUtQxPXM6iODVfqMwsVMgTbG/ZXIdQ=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 h1:hb5KgeYfObi5MHkSSZMEudnIvX30iB+E21evI4r6BnQ=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12/go.mod h1:CroKe/eWJdyfy9Vx4rljP5wTUjNJfb+fPz1uMYUhEGM=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
@@ -53,6 +59,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 h1:6cnno47Me9bRykw9AEv9zkXE+5or7
 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1/go.mod h1:qmdkIIAC+GCLASF7R2whgNrJADz0QZPX+Seiw/i4S3o=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.6 h1:TIOEjw0i2yyhmhRry3Oeu9YtiiHWISZ6j/irS1W3gX4=
 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.6/go.mod h1:3Ba++UwWd154xtP4FRX5pUK3Gt4up5sDHCve6kVfE+g=
+github.com/aws/aws-sdk-go-v2/service/ses v1.23.1 h1:XDy5gu6vWlLrR964J3yOoefbuXPEjdMglBqeANCN3Do=
+github.com/aws/aws-sdk-go-v2/service/ses v1.23.1/go.mod h1:V6akueJRZRsIvbSoFZha+H2n8ZhNcjlfR1rxuU2hZug=
 github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4 h1:mE2ysZMEeQ3ulHWs4mmc4fZEhOfeY1o6QXAfDqjbSgw=
 github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4/go.mod h1:lCN2yKnj+Sp9F6UzpoPPTir+tSaC9Jwf6LcmTqnXFZw=
 github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
@@ -163,6 +171,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kinbiko/jsonassert v1.0.1/go.mod h1:QRwBwiAsrcJpjw+L+Q4WS8psLxuUY+HylVZS/4j74TM=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -206,6 +215,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prozz/aws-embedded-metrics-golang v1.2.0 h1:b/LFb8J9LbgANow/9nYZE3M3bkb457/dj0zAB3hPyvo=
+github.com/prozz/aws-embedded-metrics-golang v1.2.0/go.mod h1:MXOqF9cJCEHjj77LWq7NWK44/AOyaFzwmcAYqR3057M=
 github.com/r3labs/diff/v2 v2.14.2 h1:1HVhQKwg1YnoCWzCYlOWYLG4C3yfTudZo5AcrTSgCTc=
 github.com/r3labs/diff/v2 v2.14.2/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
 github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
@@ -388,12 +399,16 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
 gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
diff --git a/ses/ses.go b/ses/ses.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b281ca83706f2669322cc33a4eb02f8ecc8e455
--- /dev/null
+++ b/ses/ses.go
@@ -0,0 +1,215 @@
+package ses
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/config"
+	"github.com/aws/aws-sdk-go-v2/service/ses"
+	sesTypes "github.com/aws/aws-sdk-go-v2/service/ses/types"
+	"github.com/prozz/aws-embedded-metrics-golang/emf"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/s3"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/utils"
+	"gopkg.in/gomail.v2"
+	"io"
+	"strings"
+)
+
+const (
+	InvalidParameterValueErrorString = "InvalidParameterValue"
+)
+
+var (
+	clients = map[string]*ClientWithHelpers{}
+
+	ErrorSESClientNotEstablished = errors.Error("could not establish an SES client")
+)
+
+type ClientWithHelpers struct {
+	SESClient *ses.Client
+}
+
+type Email struct {
+	Recipient           string                 `json:"recipient"`
+	FromName            string                 `json:"from_name"`
+	FromEmail           string                 `json:"from_email"`
+	Subject             string                 `json:"subject"`
+	EmailCcName         string                 `json:"email_cc_name"`
+	EmailCcAddress      string                 `json:"email_cc_address"`
+	ReplyToEmailAddress string                 `json:"reply_to_email_address"`
+	Body                EmailBody              `json:"body"`
+	Attachments         *[]s3.S3UploadResponse `json:"attachments"`
+}
+
+type EmailBody struct {
+	HTMLBody string `json:"html_body"`
+	TextBody string `json:"text_body"`
+}
+
+func GetClient(region ...string) *ClientWithHelpers {
+	sesRegion := "eu-west-1"
+
+	// Set custom region
+	if region != nil && len(region) > 0 {
+		sesRegion = region[0]
+	}
+
+	// Check if client exists for region, if it does return it
+	if sesClient, ok := clients[sesRegion]; ok && sesClient != nil {
+		return sesClient
+	}
+
+	cfg, err := config.LoadDefaultConfig(context.TODO(),
+		config.WithRegion(sesRegion),
+	)
+	if err != nil {
+		return &ClientWithHelpers{}
+	}
+
+	sesClient := NewClient(cfg)
+
+	clients[sesRegion] = sesClient
+	return sesClient
+}
+
+func NewClient(cfg aws.Config) *ClientWithHelpers {
+	return &ClientWithHelpers{
+		SESClient: ses.NewFromConfig(cfg),
+	}
+}
+
+func (c *ClientWithHelpers) SendEmail(email Email, emfNamespace string, isDebug bool) (*ses.SendRawEmailOutput, error) {
+	if c.SESClient == nil {
+		return nil, ErrorSESClientNotEstablished
+	}
+
+	recipients := utils.SplitAndCleanEmailAddresses(email.Recipient)
+	if len(recipients) == 0 {
+		return nil, nil
+	}
+
+	// Create raw message
+	msg := gomail.NewMessage()
+	msg.SetHeader("To", recipients...)
+
+	// Set the from header, and use gomail's internal function if we have a name and email
+	from := email.FromEmail
+	if email.FromName != "" {
+		from = msg.FormatAddress(email.FromEmail, email.FromName)
+	}
+	msg.SetHeader("From", from)
+	msg.SetAddressHeader("Cc", email.EmailCcAddress, email.EmailCcName)
+	msg.SetHeader("Subject", email.Subject)
+	msg.SetBody("text/plain", email.Body.TextBody) // See comment for `AddAlternative`
+	msg.AddAlternative("text/html", email.Body.HTMLBody)
+
+	// Set the reply-to address
+	if email.ReplyToEmailAddress != "" {
+		msg.SetHeader("Reply-To", email.ReplyToEmailAddress)
+	}
+
+	// Add attachments
+	if email.Attachments != nil && len(*email.Attachments) != 0 {
+		for _, attachment := range *email.Attachments {
+			err := addAttachmentsToEmail(msg, attachment, isDebug)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	// create a new buffer to add raw data
+	var emailRaw bytes.Buffer
+	_, err := msg.WriteTo(&emailRaw)
+	if err != nil {
+		return nil, err
+	}
+
+	// Append the CC address to the recipients
+	if email.EmailCcAddress != "" {
+		recipients = append(recipients, email.EmailCcAddress)
+	}
+
+	// create new raw message
+	rawEmailInput := &ses.SendRawEmailInput{
+		Source:       &from,
+		Destinations: recipients,
+		RawMessage: &sesTypes.RawMessage{
+			Data: emailRaw.Bytes(),
+		},
+	}
+
+	// Send it
+	result, err := c.SESClient.SendRawEmail(context.TODO(), rawEmailInput)
+	if err != nil {
+		messageRejectedErrorCode := errors.AWSErrorExceptionCode(&sesTypes.MessageRejected{})
+		mailFromDomainNotVerifiedErrorCode := errors.AWSErrorExceptionCode(&sesTypes.MailFromDomainNotVerifiedException{})
+		configurationSetDoesNotExistErrorCode := errors.AWSErrorExceptionCode(&sesTypes.ConfigurationSetDoesNotExistException{})
+
+		switch errors.AWSErrorExceptionCode(err) {
+		case messageRejectedErrorCode, mailFromDomainNotVerifiedErrorCode, configurationSetDoesNotExistErrorCode:
+			logs.ErrorWithMsg(errors.AWSErrorExceptionCode(err), err)
+		case InvalidParameterValueErrorString:
+			// Ignore errors due to invalid email addresses - we don't want to log it to raygun
+			logs.Warn(err.Error())
+			err = nil
+		default:
+			logs.ErrorWithMsg("Failed to send email", err)
+		}
+
+		if emfNamespace != "" {
+			emf.New().
+				Namespace(emfNamespace).
+				MetricAs("email_failed", 1, emf.Count).
+				Log()
+		}
+	} else {
+		if emfNamespace != "" {
+			emf.New().
+				Namespace(emfNamespace).
+				MetricAs("email_sent", 1, emf.Count).
+				Log()
+		}
+	}
+
+	return result, err
+}
+
+func addAttachmentsToEmail(msg *gomail.Message, attachment s3.S3UploadResponse, isDebug bool) error {
+	if attachment.Bucket == "" || attachment.Filename == "" {
+		logs.Warn("empty bucket and/or filename, cannot attach file")
+		return nil
+	}
+
+	// Get object from S3
+	object, err := s3.GetClient(isDebug).GetObject(attachment.Bucket, attachment.Filename, isDebug)
+	if err != nil {
+		logs.ErrorWithMsg(fmt.Sprintf("Failed to get S3 object from bucket %s filename %s", attachment.Bucket, attachment.Filename), err)
+		return err
+	}
+	defer object.Body.Close()
+
+	attachmentBytes, err := io.ReadAll(object.Body)
+	if err != nil {
+		logs.ErrorWithMsg("Could not encode attachment for email", err)
+		return err
+	}
+
+	// Get the filename
+	splitString := strings.Split(attachment.Filename, "/")
+	filename := splitString[len(splitString)-1]
+
+	msg.Attach(
+		filename,
+		gomail.SetCopyFunc(func(w io.Writer) error {
+			_, err := w.Write(attachmentBytes)
+			return err
+		}),
+		gomail.SetHeader(map[string][]string{"Content-Type": {*object.ContentType}}),
+	)
+
+	return nil
+}