Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/44214.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-action
aws_ses_send_email
```
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/hcl/v2 v2.24.0
github.com/hashicorp/terraform-json v0.27.2
github.com/hashicorp/terraform-plugin-framework v1.16.0
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0
Expand All @@ -300,7 +300,7 @@ require (
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.21.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0
github.com/hashicorp/terraform-plugin-testing v1.13.3
github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247
github.com/jaswdr/faker/v2 v2.8.0
github.com/jmespath/go-jmespath v0.4.0
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38
Expand Down Expand Up @@ -349,7 +349,7 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/hc-install v0.9.2 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.23.0 // indirect
github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2 // indirect
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
Expand All @@ -368,7 +368,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zclconf/go-cty v1.16.4 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -661,12 +661,12 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24=
github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I=
github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY=
github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2 h1:90fcAqw0Qmv4vY7zL4jEKgKarHmOnNN6SjTY68eLKGA=
github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2/go.mod h1:8D3RLLpzAZdhT9jvALYz1KHyGU4OvI73I1o0+01QJxA=
github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=
github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
github.com/hashicorp/terraform-plugin-framework v1.16.0 h1:tP0f+yJg0Z672e7levixDe5EpWwrTrNryPM9kDMYIpE=
Expand All @@ -685,8 +685,8 @@ github.com/hashicorp/terraform-plugin-mux v0.21.0 h1:QsEYnzSD2c3zT8zUrUGqaFGhV/Z
github.com/hashicorp/terraform-plugin-mux v0.21.0/go.mod h1:Qpt8+6AD7NmL0DS7ASkN0EXpDQ2J/FnnIgeUr1tzr5A=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA=
github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo=
github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE=
github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247 h1:lA6ofPwmCXAX7J7kVP9t/WMU5+eA4e9YvJUiRLPdENw=
github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247/go.mod h1:4r/7cxl1mpskfALcq58Iyu5aPiTSco8SVrKkcLyP5g4=
github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
Expand Down Expand Up @@ -790,8 +790,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE=
github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
Expand Down
199 changes: 199 additions & 0 deletions internal/service/ses/send_email_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ses

import (
"context"
"fmt"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ses"
awstypes "github.com/aws/aws-sdk-go-v2/service/ses/types"
"github.com/hashicorp/terraform-plugin-framework/action"
"github.com/hashicorp/terraform-plugin-framework/action/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @Action(aws_ses_send_email, name="Send Email")
func newSendEmailAction(_ context.Context) (action.ActionWithConfigure, error) {
return &sendEmailAction{}, nil
}

var (
_ action.Action = (*sendEmailAction)(nil)
)

type sendEmailAction struct {
framework.ActionWithModel[sendEmailActionModel]
}

type sendEmailActionModel struct {
framework.WithRegionModel
Source types.String `tfsdk:"source"`
ToAddresses fwtypes.ListValueOf[types.String] `tfsdk:"to_addresses"`
CcAddresses fwtypes.ListValueOf[types.String] `tfsdk:"cc_addresses"`
BccAddresses fwtypes.ListValueOf[types.String] `tfsdk:"bcc_addresses"`
Subject types.String `tfsdk:"subject"`
TextBody types.String `tfsdk:"text_body"`
HtmlBody types.String `tfsdk:"html_body"`
ReplyToAddresses fwtypes.ListValueOf[types.String] `tfsdk:"reply_to_addresses"`
ReturnPath types.String `tfsdk:"return_path"`
}

func (a *sendEmailAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Sends an email using Amazon SES. This action allows for imperative email sending with full control over recipients, content, and formatting.",
Attributes: map[string]schema.Attribute{
names.AttrSource: schema.StringAttribute{
Description: "The email address that is sending the email. This address must be either individually verified with Amazon SES, or from a domain that has been verified with Amazon SES.",
Required: true,
},
"to_addresses": schema.ListAttribute{
CustomType: fwtypes.ListOfStringType,
ElementType: types.StringType,
Description: "The To: field(s) of the message.",
Optional: true,
},
"cc_addresses": schema.ListAttribute{
CustomType: fwtypes.ListOfStringType,
ElementType: types.StringType,
Description: "The CC: field(s) of the message.",
Optional: true,
},
"bcc_addresses": schema.ListAttribute{
CustomType: fwtypes.ListOfStringType,
ElementType: types.StringType,
Description: "The BCC: field(s) of the message.",
Optional: true,
},
"subject": schema.StringAttribute{
Description: "The subject of the message: A short summary of the content, which will appear in the recipient's inbox.",
Required: true,
},
"text_body": schema.StringAttribute{
Description: "The message body in text format. Either text_body or html_body must be specified.",
Optional: true,
},
"html_body": schema.StringAttribute{
Description: "The message body in HTML format. Either text_body or html_body must be specified.",
Optional: true,
},
"reply_to_addresses": schema.ListAttribute{
CustomType: fwtypes.ListOfStringType,
ElementType: types.StringType,
Description: "The reply-to email address(es) for the message. If the recipient replies to the message, each reply-to address will receive the reply.",
Optional: true,
},
"return_path": schema.StringAttribute{
Description: "The email address that bounces and complaints will be forwarded to when feedback forwarding is enabled.",
Optional: true,
},
},
}
}

func (a *sendEmailAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var config sendEmailActionModel

resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}

// Validate that at least one body type is provided
if config.TextBody.IsNull() && config.HtmlBody.IsNull() {
resp.Diagnostics.AddError(
"Missing Email Body",
"Either text_body or html_body must be specified",
)
return
}

conn := a.Meta().SESClient(ctx)

source := config.Source.ValueString()
subject := config.Subject.ValueString()

tflog.Info(ctx, "Starting SES send email action", map[string]any{
names.AttrSource: source,
"subject": subject,
"has_text_body": !config.TextBody.IsNull(),
"has_html_body": !config.HtmlBody.IsNull(),
})

resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Sending email from %s...", source),
})

// Build destination
destination := &awstypes.Destination{}
if !config.ToAddresses.IsNull() {
destination.ToAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.ToAddresses)
}
if !config.CcAddresses.IsNull() {
destination.CcAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.CcAddresses)
}
if !config.BccAddresses.IsNull() {
destination.BccAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.BccAddresses)
}

// Build message
message := &awstypes.Message{
Subject: &awstypes.Content{
Data: aws.String(subject),
},
Body: &awstypes.Body{},
}

if !config.TextBody.IsNull() {
message.Body.Text = &awstypes.Content{
Data: config.TextBody.ValueStringPointer(),
}
}
if !config.HtmlBody.IsNull() {
message.Body.Html = &awstypes.Content{
Data: config.HtmlBody.ValueStringPointer(),
}
}

// Build input
input := &ses.SendEmailInput{
Source: aws.String(source),
Destination: destination,
Message: message,
}

if !config.ReplyToAddresses.IsNull() {
input.ReplyToAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.ReplyToAddresses)
}

if !config.ReturnPath.IsNull() {
input.ReturnPath = config.ReturnPath.ValueStringPointer()
}

// Send email
output, err := conn.SendEmail(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"Failed to Send Email",
fmt.Sprintf("Could not send email from %s: %s", source, err),
)
return
}

messageId := aws.ToString(output.MessageId)
resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Email sent successfully (Message ID: %s)", messageId),
})

tflog.Info(ctx, "SES send email action completed successfully", map[string]any{
names.AttrSource: source,
"message_id": messageId,
})
}
Loading
Loading