Skip to content

Commit 1f4e8b1

Browse files
authored
Merge pull request #44214 from hashicorp/f-aws_ses_send_email
New action: `aws_ses_send_email`
2 parents 2e87167 + f1f8645 commit 1f4e8b1

File tree

5 files changed

+593
-0
lines changed

5 files changed

+593
-0
lines changed

.changelog/44214.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-action
2+
aws_ses_send_email
3+
```
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package ses
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/aws/aws-sdk-go-v2/aws"
11+
"github.com/aws/aws-sdk-go-v2/service/ses"
12+
awstypes "github.com/aws/aws-sdk-go-v2/service/ses/types"
13+
"github.com/hashicorp/terraform-plugin-framework/action"
14+
"github.com/hashicorp/terraform-plugin-framework/action/schema"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
"github.com/hashicorp/terraform-plugin-log/tflog"
17+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
18+
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
19+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
20+
"github.com/hashicorp/terraform-provider-aws/names"
21+
)
22+
23+
// @Action(aws_ses_send_email, name="Send Email")
24+
func newSendEmailAction(_ context.Context) (action.ActionWithConfigure, error) {
25+
return &sendEmailAction{}, nil
26+
}
27+
28+
var (
29+
_ action.Action = (*sendEmailAction)(nil)
30+
)
31+
32+
type sendEmailAction struct {
33+
framework.ActionWithModel[sendEmailActionModel]
34+
}
35+
36+
type sendEmailActionModel struct {
37+
framework.WithRegionModel
38+
Source types.String `tfsdk:"source"`
39+
ToAddresses fwtypes.ListValueOf[types.String] `tfsdk:"to_addresses"`
40+
CcAddresses fwtypes.ListValueOf[types.String] `tfsdk:"cc_addresses"`
41+
BccAddresses fwtypes.ListValueOf[types.String] `tfsdk:"bcc_addresses"`
42+
Subject types.String `tfsdk:"subject"`
43+
TextBody types.String `tfsdk:"text_body"`
44+
HtmlBody types.String `tfsdk:"html_body"`
45+
ReplyToAddresses fwtypes.ListValueOf[types.String] `tfsdk:"reply_to_addresses"`
46+
ReturnPath types.String `tfsdk:"return_path"`
47+
}
48+
49+
func (a *sendEmailAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
50+
resp.Schema = schema.Schema{
51+
Description: "Sends an email using Amazon SES. This action allows for imperative email sending with full control over recipients, content, and formatting.",
52+
Attributes: map[string]schema.Attribute{
53+
names.AttrSource: schema.StringAttribute{
54+
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.",
55+
Required: true,
56+
},
57+
"to_addresses": schema.ListAttribute{
58+
CustomType: fwtypes.ListOfStringType,
59+
ElementType: types.StringType,
60+
Description: "The To: field(s) of the message.",
61+
Optional: true,
62+
},
63+
"cc_addresses": schema.ListAttribute{
64+
CustomType: fwtypes.ListOfStringType,
65+
ElementType: types.StringType,
66+
Description: "The CC: field(s) of the message.",
67+
Optional: true,
68+
},
69+
"bcc_addresses": schema.ListAttribute{
70+
CustomType: fwtypes.ListOfStringType,
71+
ElementType: types.StringType,
72+
Description: "The BCC: field(s) of the message.",
73+
Optional: true,
74+
},
75+
"subject": schema.StringAttribute{
76+
Description: "The subject of the message: A short summary of the content, which will appear in the recipient's inbox.",
77+
Required: true,
78+
},
79+
"text_body": schema.StringAttribute{
80+
Description: "The message body in text format. Either text_body or html_body must be specified.",
81+
Optional: true,
82+
},
83+
"html_body": schema.StringAttribute{
84+
Description: "The message body in HTML format. Either text_body or html_body must be specified.",
85+
Optional: true,
86+
},
87+
"reply_to_addresses": schema.ListAttribute{
88+
CustomType: fwtypes.ListOfStringType,
89+
ElementType: types.StringType,
90+
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.",
91+
Optional: true,
92+
},
93+
"return_path": schema.StringAttribute{
94+
Description: "The email address that bounces and complaints will be forwarded to when feedback forwarding is enabled.",
95+
Optional: true,
96+
},
97+
},
98+
}
99+
}
100+
101+
func (a *sendEmailAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
102+
var config sendEmailActionModel
103+
104+
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
105+
if resp.Diagnostics.HasError() {
106+
return
107+
}
108+
109+
// Validate that at least one body type is provided
110+
if config.TextBody.IsNull() && config.HtmlBody.IsNull() {
111+
resp.Diagnostics.AddError(
112+
"Missing Email Body",
113+
"Either text_body or html_body must be specified",
114+
)
115+
return
116+
}
117+
118+
conn := a.Meta().SESClient(ctx)
119+
120+
source := config.Source.ValueString()
121+
subject := config.Subject.ValueString()
122+
123+
tflog.Info(ctx, "Starting SES send email action", map[string]any{
124+
names.AttrSource: source,
125+
"subject": subject,
126+
"has_text_body": !config.TextBody.IsNull(),
127+
"has_html_body": !config.HtmlBody.IsNull(),
128+
})
129+
130+
resp.SendProgress(action.InvokeProgressEvent{
131+
Message: fmt.Sprintf("Sending email from %s...", source),
132+
})
133+
134+
// Build destination
135+
destination := &awstypes.Destination{}
136+
if !config.ToAddresses.IsNull() {
137+
destination.ToAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.ToAddresses)
138+
}
139+
if !config.CcAddresses.IsNull() {
140+
destination.CcAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.CcAddresses)
141+
}
142+
if !config.BccAddresses.IsNull() {
143+
destination.BccAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.BccAddresses)
144+
}
145+
146+
// Build message
147+
message := &awstypes.Message{
148+
Subject: &awstypes.Content{
149+
Data: aws.String(subject),
150+
},
151+
Body: &awstypes.Body{},
152+
}
153+
154+
if !config.TextBody.IsNull() {
155+
message.Body.Text = &awstypes.Content{
156+
Data: config.TextBody.ValueStringPointer(),
157+
}
158+
}
159+
if !config.HtmlBody.IsNull() {
160+
message.Body.Html = &awstypes.Content{
161+
Data: config.HtmlBody.ValueStringPointer(),
162+
}
163+
}
164+
165+
// Build input
166+
input := &ses.SendEmailInput{
167+
Source: aws.String(source),
168+
Destination: destination,
169+
Message: message,
170+
}
171+
172+
if !config.ReplyToAddresses.IsNull() {
173+
input.ReplyToAddresses = fwflex.ExpandFrameworkStringValueList(ctx, config.ReplyToAddresses)
174+
}
175+
176+
if !config.ReturnPath.IsNull() {
177+
input.ReturnPath = config.ReturnPath.ValueStringPointer()
178+
}
179+
180+
// Send email
181+
output, err := conn.SendEmail(ctx, input)
182+
if err != nil {
183+
resp.Diagnostics.AddError(
184+
"Failed to Send Email",
185+
fmt.Sprintf("Could not send email from %s: %s", source, err),
186+
)
187+
return
188+
}
189+
190+
messageId := aws.ToString(output.MessageId)
191+
resp.SendProgress(action.InvokeProgressEvent{
192+
Message: fmt.Sprintf("Email sent successfully (Message ID: %s)", messageId),
193+
})
194+
195+
tflog.Info(ctx, "SES send email action completed successfully", map[string]any{
196+
names.AttrSource: source,
197+
"message_id": messageId,
198+
})
199+
}

0 commit comments

Comments
 (0)