From: Andrew Gundersen Date: Mon, 2 Mar 2026 03:08:26 +0000 (-0500) Subject: Replace AWS SES with Resend for email delivery X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=4841986e0bd6d3eb8443eabc7edb1b591b0c5c49;p=crimata-infra Replace AWS SES with Resend for email delivery Co-Authored-By: Claude Sonnet 4.6 --- diff --git a/cmd/server/main.go b/cmd/server/main.go index ffbc4be..3af8b02 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -49,8 +49,9 @@ func main() { BaseDomain: getEnv("BASE_DOMAIN", "crimata.com"), }) - notifyClient := notify.NewClient(awsCfg, notify.Config{ - FromEmail: getEnv("SES_FROM_EMAIL", "noreply@crimata.com"), + notifyClient := notify.NewClient(notify.Config{ + ApiKey: mustEnv("RESEND_API_KEY"), + FromEmail: getEnv("MAILER_FROM", "hello@crimata.com"), BaseDomain: getEnv("BASE_DOMAIN", "crimata.com"), }) diff --git a/go.mod b/go.mod index f2ba217..8a34ad4 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2 v1.151.0 github.com/aws/aws-sdk-go-v2/service/route53 v1.40.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 - github.com/aws/aws-sdk-go-v2/service/ses v1.22.0 github.com/aws/aws-sdk-go-v2/service/ssm v1.49.0 github.com/go-chi/chi/v5 v5.1.0 github.com/lib/pq v1.10.9 + github.com/resend/resend-go/v2 v2.28.0 golang.org/x/crypto v0.48.0 ) diff --git a/go.sum b/go.sum index 42f08ff..217b057 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,6 @@ github.com/aws/aws-sdk-go-v2/service/route53 v1.40.0 h1:MRriK+ntpKpUc8RwcYJbc5W/ github.com/aws/aws-sdk-go-v2/service/route53 v1.40.0/go.mod h1:n6oZO1BbhPw2X46ObAjn8ol00kujRT+Y+Q9AnbrRUe0= github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0 h1:r3o2YsgW9zRcIP3Q0WCmttFVhTuugeKIvT5z9xDspc0= github.com/aws/aws-sdk-go-v2/service/s3 v1.53.0/go.mod h1:w2E4f8PUfNtyjfL6Iu+mWI96FGttE03z3UdNcUEC4tA= -github.com/aws/aws-sdk-go-v2/service/ses v1.22.0 h1:1HJMg3WpV9L003TOhH8eEQgkPnegOwCE3/j0Iqlmt0U= -github.com/aws/aws-sdk-go-v2/service/ses v1.22.0/go.mod h1:9MbVy83ucnbP+4D9lky3IeUBxjECR2bHq63yzkl+WKU= github.com/aws/aws-sdk-go-v2/service/ssm v1.49.0 h1:EtNvvxv0m6aP4cbTyo43vBRXeTpyt8juyNPmgKSTyYs= github.com/aws/aws-sdk-go-v2/service/ssm v1.49.0/go.mod h1:wzPAvA+afHPFlAMkCf80sg7bm7GbCuFX1INetlm9DAk= github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 h1:u6OkVDxtBPnxPkZ9/63ynEe+8kHbtS5IfaC4PzVxzWM= @@ -42,8 +40,9 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.27.0 h1:cjTRjh700H36MQ8M0LnDn33W3Jmw github.com/aws/aws-sdk-go-v2/service/sts v1.27.0/go.mod h1:nXfOBMWPokIbOY+Gi7a1psWMSvskUCemZzI+SMB7Akc= github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= @@ -56,7 +55,11 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/resend/resend-go/v2 v2.28.0 h1:ttM1/VZR4fApBv3xI1TneSKi1pbfFsVrq7fXFlHKtj4= +github.com/resend/resend-go/v2 v2.28.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= @@ -66,3 +69,5 @@ golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/notify/notify.go b/internal/notify/notify.go index 48bf1f1..ebe6792 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -4,63 +4,47 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/ses" - sestypes "github.com/aws/aws-sdk-go-v2/service/ses/types" + resend "github.com/resend/resend-go/v2" ) type Config struct { + ApiKey string FromEmail string BaseDomain string } type Client struct { - ses *ses.Client - cfg Config + resend *resend.Client + cfg Config } -func NewClient(awsCfg aws.Config, cfg Config) *Client { +func NewClient(cfg Config) *Client { return &Client{ - ses: ses.NewFromConfig(awsCfg), - cfg: cfg, + resend: resend.NewClient(cfg.ApiKey), + cfg: cfg, } } -func (c *Client) SendWelcome(ctx context.Context, email, slug, password string) error { +func (c *Client) SendWelcome(_ context.Context, email, slug, password string) error { url := fmt.Sprintf("https://%s.%s", slug, c.cfg.BaseDomain) - subject := "Your Crimata hub is ready" - text := fmt.Sprintf("Your hub is live at %s\n\nPassword: %s\n\nChange your password in Settings after logging in.\n\n— Crimata", url, password) - html := fmt.Sprintf(`

Your hub is live at %s

Password: %s

Change your password in Settings after logging in.

— Crimata

`, url, url, password) - _, err := c.ses.SendEmail(ctx, &ses.SendEmailInput{ - Source: aws.String(c.cfg.FromEmail), - Destination: &sestypes.Destination{ToAddresses: []string{email}}, - Message: &sestypes.Message{ - Subject: &sestypes.Content{Data: aws.String(subject)}, - Body: &sestypes.Body{ - Text: &sestypes.Content{Data: aws.String(text)}, - Html: &sestypes.Content{Data: aws.String(html)}, - }, - }, + _, err := c.resend.Emails.Send(&resend.SendEmailRequest{ + From: c.cfg.FromEmail, + To: []string{email}, + Subject: "Your Crimata hub is ready", + Text: fmt.Sprintf("Your hub is live at %s\n\nUsername: %s\nPassword: %s\n\nChange your password in Settings after logging in.\n\n— Crimata", url, slug, password), + Html: fmt.Sprintf(`

Your hub is live at %s

Username: %s
Password: %s

Change your password in Settings after logging in.

— Crimata

`, url, url, slug, password), }) return err } -func (c *Client) SendDataExport(ctx context.Context, email, downloadURL string) error { - subject := "Your Crimata data export is ready" - text := fmt.Sprintf("Your data export is ready for download:\n\n%s\n\nThis link expires in 24 hours.\n\n— Crimata", downloadURL) - html := fmt.Sprintf(`

Your data export is ready: Download

This link expires in 24 hours.

— Crimata

`, downloadURL) - - _, err := c.ses.SendEmail(ctx, &ses.SendEmailInput{ - Source: aws.String(c.cfg.FromEmail), - Destination: &sestypes.Destination{ToAddresses: []string{email}}, - Message: &sestypes.Message{ - Subject: &sestypes.Content{Data: aws.String(subject)}, - Body: &sestypes.Body{ - Text: &sestypes.Content{Data: aws.String(text)}, - Html: &sestypes.Content{Data: aws.String(html)}, - }, - }, +func (c *Client) SendDataExport(_ context.Context, email, downloadURL string) error { + _, err := c.resend.Emails.Send(&resend.SendEmailRequest{ + From: c.cfg.FromEmail, + To: []string{email}, + Subject: "Your Crimata data export is ready", + Text: fmt.Sprintf("Your data export is ready for download:\n\n%s\n\nThis link expires in 24 hours.\n\n— Crimata", downloadURL), + Html: fmt.Sprintf(`

Your data export is ready: Download

This link expires in 24 hours.

— Crimata

`, downloadURL), }) return err }