diff --git a/.tools/ailly/docs/.aillyrc b/.tools/ailly/docs/.aillyrc new file mode 100644 index 00000000000..073672e77b7 --- /dev/null +++ b/.tools/ailly/docs/.aillyrc @@ -0,0 +1,51 @@ +--- +combined: true +overwrite: false +--- + +TCX SDK Code Examples +The TCX SDK Code Examples team produces code examples that demonstrate how to automate AWS services to accomplish key user stories for developers and programmers. These code examples are quick and easy to find and use, are continually tested, and demonstrate AWS and community coding best practices. + +Mission + +We provide code examples for builders integrating AWS services into their applications and business workflows using the AWS Software Development Kits (SDKs). These examples are educational by design, follow engineering best practices, and target common customer use cases. Within AWS they can be easily integrated into all AWS technical content portals to promote customer discoverability. + +Vision + +We envision a best-in-class library of code examples for every AWS service and in every actively maintained SDK language. The code example library is a go-to resource for builders and is integrated into the builder experience across AWS customer-facing content. Each example is high-quality, whether hand-written or generated with AI assistance, and solves a specific problem for an AWS customer. + +Tenets + +These are our tenets, unless you know better ones: + +We are educators. Comprehension and learnability always take precedence. +We are engineers. Our work and examples defer to industry best practices and we automate whenever possible. +Our examples address common user challenges. They do not deliberately mirror AWS service silos. +Our examples are discoverable. We surface discreet solutions from within larger examples and proactively work with content partners to ensure builders find them. +We are subject matter experts. We are the primary reference for code example standards in TCX. + +A Workflow Example, as defined by the TCX Code Examples team, is an example scenario that is targeted to a particular real-world user story, use case, problem, or other common service integration. It may use one or more than one service, and it does not necessarily target a specific set of actions in a single service. Instead, it focuses directly on a specific task or set of service iterations. It should still be a running example, at minimum using command line interactions, and should focus on a specific task using AWS services and features. + +Ailly - AI Writing Ally + +Load your writing. Train Ailly on your voice. Write your outline. Prompt Ailly to continue to continue the writing. Edit its output, and retrain to get it even more like that. + +Rhymes with Daily. + +Conversational History + +In LLM Chatbots like ChatGPT or chains like Langchain, the history of the conversation is kept in the sequence of human, assistant interactions. This is typically kept in memory, or at least in an inaccessible format to the user. The user can only regenerate sequences, or add their next prompt at the end. + +Ailly removes this limitation by using your file system as the conversational history. The writer maintains full control over the sequence of prompts, up to and including editing the LLM's response before (re)generating the next prompt! This lets the writer decide how the conversation should evolve. By editing an LLM prompt, they can take the best of what the LLM did in some cases, and modify it in others. Using Ailly's filesystem based conversational history, each piece of the session can be stored in source control. Version tracking lets the author see how their prompts and responses have changed over time, and unlock a number of long-term process improvements that are difficult to impossible with chat interfaces. + +In one session, a developer was working on a long sequence of prompts to build a software project. While reviewing an LLM written draft of the README, the developer wanted the list of API calls to be links to the reference documentation. With a chat conversational history, the developer would have needed to modify the instructions for the entire prompt to encourage creating the list, rerun the generation, and hope the rest of the README came out similarly. Instead, with Ailly, the developer created a new file with only the list and an instruction on how to create URLs from the list items, saved it as list.md (with isolated: true in the combined head), and ran ailly list.md. The LLM followed the instructions, generated just the updated list, and the developer copied that list into the original (generated) README.md. In later prompts, the context window included the entire URLs, and the agent model was able to intelligently request to download their contents. + +To the author's knowledge, no other LLM interface provides this level of interaction with LLMs. + +Properties + +These properties can be set in a combination of places, includeing the command line, .aillyrc, and greymatter. Later settings override earlier. + +combined boolean If true, the file's body is the response and the prompt is in the greymatter key prompt. If false, the file's body is the prompt and the response is in {file_name}.ailly.md. Default false. +skip boolean If true, the prompt will not be sent through the LLM (but it will be part of the context). +isolated boolean If true, the LLM inference will only include the system prompt, and not the prior context in this folder. diff --git a/.tools/ailly/docs/10_scout_how_to.md b/.tools/ailly/docs/10_scout_how_to.md new file mode 100644 index 00000000000..4d0b2a3f79b --- /dev/null +++ b/.tools/ailly/docs/10_scout_how_to.md @@ -0,0 +1,287 @@ +--- +skip: True +prompt: Write instructions on how to do Workflow Scouting with Ailly. +--- + +# How to Scout with Ailly + +## Prereq: Install and run Ailly + +1. On the command line, install globally as `ailly` with `npm install -g @ailly/cli` + - and can then be updated with `npm install -g @ailly/cli@{version}` + - As of this writing (2024-04-08), version is `1.2.5` or `1.2.6-rc1` +1. Choose your engine, probably bedrock. + - Export an environment variable: `export AILLY_ENGINE=bedrock`. + - Ensure you're using your AWS account (`ada` or copying the access keys from isengard) + +## Readme + +The first step is getting a feel for Ailly and working with it. We'll have it make a README for the workflow, edit the README ourselves for fine detailing, and use it in "assist" or "tmp" or "macro" mode (I need a good name for this) to do bulk editing tasks. + +1. WITHOUT using an LLM, do the Workflow process. Meet a SME, develop a high level plan for what the workflow should do. +2. Create a new workspace for your scout in the `workflow` folder. +3. Create a new folder for doing Ailly work, `workflow/scout_name/content`, and cd into it. +4. Add a `.aillyrc` file with: + - A greymatter head: + ``` + --- + combined: true + --- + ``` + - A level-setting prompt (Coming soon!) to guide output format. + - The summary of the Code Examples team. + - A summary of the service you're writing the workflow for. + - The summary of the workflow. + - A list of the API calls you expect to make. +5. Add a file, `01_README.md`, with a greymatter head: + ``` + --- + prompt: Create a README.md for this project + --- + ``` + - at any (and every?) point in this process, experiment with your own prompting! +6. Run ailly: `ailly 10_README.md` +7. Review the output. Correct it as desired. + + - If there's a thing you want to change, you can use Ailly like a macro + - Let's say Ailly creates a list of the API calls to use, but doesn't include links + - Create a folder, `tmp` + - Create an aillyrc, `tmp/.aillyrc`, with + ``` + --- + isolated: true + --- + ``` + - Create a file, `tmp/links.md` + - Add this head: + + ``` + --- + prompt: | + Reformat this list into links to the documentation. For instance, the SendEmail item should link to https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html. + + SendEmail + CreateEmailIdentity + [paste the rest of your items] + --- + ``` + + - Run Ailly: `ailly tmp/links.md` + - Review the output, which should now have + ``` + * [SendEmail](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html) + * [CreateEmailIdentity](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailIdentity.html) + * [The rest of your links] + ``` + - If this doesn't work the first time, rerun and see if it helps. + - If it doesn't a second time, play with the prompt until it does what you want. + - Paste the output back into the `01_README.md` document. + +## First Spec + +At this point, you should have a good start on the README, which we can use to have Ailly write the spec. From current experiments, LLM generation works best with ~500 word output "chunks", so we start planning our Ailly calls around that. It also works best having additional context, so we'll get that first. + +1. Help Ailly get API docs for the service. + + - Create a new file, `tmp/get_api.md` + - Add a prompt: + + ``` + --- + prompt: | + Write a (bash or powershell or batch (or python?)) script to download the API docs for each API used in this workflow. + + An API doc link looks like https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html + + After downloading, pass the HTML files first through `pup` to select '#main-content', then use `html2text -nobs -utf8` to get just the main text of the document. + + Put the downloaded text in a file, 10_{ApiName}.md, with a greymatter header that has a property `skip: true`. + + For example, `echo "---\nskip: true\n---" > 10_SendEmail.md\ncurl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html | pup "#main-content" | html2text -nobs -utf8 >> 10_SendEmail.md` + --- + ``` + + - This kind of prompt can take a bit of work to get right. In my experience, I spend a couple minutes digging into structure of the document, then use the command line tools like `pup`, `jq`, `yq`, `html2text`, `pandoc` + - It might be faster to just copy/paste it a handful of times, but that's not as fun + - Why spend 2 minutes doing repetitive tasks when we could spend 15 minutes learning how to make the LLM do the repetitive task for us? + - To run the commands it generates, I just copy/paste them into my terminal. #yolo + - Real #yolo will come maybe next quarter when I add a tools module to ailly to give it direct shell access + - However you do it, make sure you end up with a few `10_API.md` files in your `content/` folder. These will be used by the remaining prompts. + +2. Create the first part of the spec, `30_SPECIFICATION.md` (I left the 20s available for future use...) + + - Contents: + + ``` + --- + prompt: | + Write a specification for this workflow. + + [Lots of instructions for how you want it to handle the workflow!] + + [How should it handle input? Prompt, or variables? Team says prompt...] + + [Share what works and what doesn't! We can build a library of these!] + + [Example:] + + The specification is independent of any programming language, and should enable any programmer competent with programming using any published AWS SDK to follow along. It must specify the API calls to make, and it must include the parameters to send. It should describe the parameters in a list format. Implementations will use the specific SDKs, so it does not need to specify URL calls to make, only the API calls and the request parameters to include. It must specify the exact environment variable names and files to use when referring to runtime data. + --- + ``` + + - Run Ailly! `ailly 30_specification.md` + - As we keep specifying the file name, Ailly will load the entire directory before this file as context, but only generate this file as output. + - This output will, keeping with the "500 words", probably be pretty close to the README but a bit more formal. That's OK, we're going to build from here. + - Iterate the prompt until you get a result you like. Keep copies or git commits, as you prefer, if you want. + - When you have one you like, edit it a bit. + +3. Create specific parts of the spec. If the workflow has five "parts", create `31_PART_1.md`, `32_PART_2.md`, etc. (Recommend replacing `PART_1` with the short name of the part or step.) + + - For each, this is the prompt I started with: + ``` + --- + prompt: | + Describe the exact SESv2 API calls and parameters for this step. + ## Prepare the Application + --- + ``` + - This prompt kinda sucked, TBH, but it got the job done. Lots of room for improvement here. + - Run Ailly: `ailly 31_PART_1.md` (or `ailly 3{1,2,3}*.md`). + - Iterate! + +4. If the spec needs sample files, have Ailly & Claude make them! + + - `50_SAMPLE_FILES.md` + ``` + --- + prompt: | + List and describe the sample files that this workflow will need at runtime. + --- + ``` + - `51_SAMPLE_FILE_A.md` + ``` + --- + prompt: | + Create [Sample File A] + --- + ``` + - etc + +5. Consolidate these files + - Maybe in the future Ailly can have file system access and know how to issue instructions to combine the various in-progess files, but for now I just open the handful of files and copy/paste from `content/01_README.md` to `README.md`. + - Open a PR and review the workflow spec + +# First Language + +## Structure + +1. Create a folder for the language, say, `python`, and add an `.aillyrc`: + + ``` + You are a Python programmer, using Python 3.9. + + + + ...python... + + + + ...python... + + + + ...python... + + + + ...python... + + + + ...python... + + + + + Imports should be sorted. [Additional notes as necessary] + ``` + + You may or may not include snippets for these, but they should help. If there are other examples you find help a lot, let the team know! + + Anthropic claims [claude does best with XML tags](https://docs.anthropic.com/claude/docs/long-context-window-tips)? But I've seen it do fine with markdown? So I'm playing with both, preferring Markdown for quick self contained things, and XML for longer/larger/more detailed examples. + + - When running ailly, run it from the folder with the original `.aillyrc` - `ailly python/20_PLAN.md`. + +1. Copy the current files. Ailly uses `.aillyrc` files going up, but only includes files in the current folder for the current context. + - Copy the README.md and SPECIFICATION.md consolidated files to `01_README.md` and `02_SPECIFICATION.md`. +1. Find the language-specific API documentation, and put it into `10_[API_CALL].md`. + - Add greymatter to each with `skip: true` (don't want to be regenerating these.) +1. Create a `20_PLAN.md`. Your plan should have a prompt asking for the implementation files and class & method stubs, but not full implementations + - Generate the plan. This might take a few passes. Maybe add some examples for how you want it structured. Eventually you should come out with an outline for the project you're satisfied with. + - You can ask it to make the plan in the form of a shell script that would create the files it wants - this can be helpful if you know you'll need a few files for different parts. + - Maybe create `21b_PLAN_SUPPLEMENTAL.md` with a `skip: true` and prompt: "When implementing methods, only implement the method you're currently instructed to implement." There may be other supplemental details as well. + - The important part here is to remind you you can slip in additional details for all downstream instructions in a few different ways. + - When running ailly, run it from the folder with the original `.aillyrc` - `ailly python/20_PLAN.md`. +1. Create `21{a,b,c}_PLAN_DETAILS.md` + + - Write a prompt that instructs the model to write itself prompts: + + ``` + Based on the plan, create a human prompt for each method in the SES2Mailer class, as well as the main method. + + Format your output as a shell heredoc cat that writes the prompt into a markdown file. + + The markdown should have yaml greymatter with two properties - `skip: true` and `prompt: ` with the content you generate. + + The prompt will be used as the `human` side of an LLM conversation. + + The file should get written to `51_{function_name}.md`. + + Start with these methods: + - def create_email_identity(self): + - def create_contact_list(self, contact_list_name): + [... etc, copied from the plan] + ``` + + - This is the first "model writing for the model" step. + - After running, you should get a shell script that will make these files. + - You can also make the files yourselves + +1. Create and generate the `51_{function name}.md` files from the PLAN_DETAILS step. +1. Repeat for testing (or did you proactively add testing in the original plan? Nice!) +1. Consolidate everything you have at this point to a new project. Maybe have Ailly write a script that does it for you? + +## Run and Test + +1. Create a new file, and paste the generated code in. +1. Use IDE tooling to fix type issues; run it, test it, etc. +1. Copy your edits back into the output, and add `skip: true` to lock them in. +1. Iterate. If your plan had 4 functions, but you wanted 5, edit the plan and rerun the steps after the plan. +1. If your main function did some special set up, edit it as well. +1. Edit the "first" function with your patterns and best practices. This will 'level set' future answers for running after this step. +1. Continue iteratively prompting for the next step of the plan, copying it to the working directory, running, testing, editing, and copying back. + +> This process sucks, but [I don't know how to fix it yet](https://github.com/DavidSouther/ailly/issues/18). Ideas and PRs welcome. + +# Second Language + +1. Copy all your work from the first language to a second language. +1. Keep `skip: true` on README, Spec, and other language independent files. +1. Redo the API lookup and extraction. +1. Replace all first language- prompt words with the second - eg "write python to" with "write java to" +1. Rerun for the second language. +1. Re-iterate for the Run and Test steps. + +# Metadata + +1. Provide examples of metadata? +1. Provide metadata docs and instruction? +1. Ask for a patch to add snippet tags? + - This didn't actually work - the patch rarely applies cleanly, and the locations aren't what I'd want. + +# PLAN + +- **Prepare** a precise prompt (by writing an aillyrc system prompt, providing supporting documents, and giving individual prompt steps). +- **Leverage** LLM models (by running Ailly on some or all parts of the context chain). +- **Assess** the generated content (as Ailly and the LLM writes output, make sure it's on the right track). +- **Narrow** your context (by editing Ailly's generated content to keep the conversation going where you want it to). diff --git a/.tools/ailly/docs/20_PLAN.md b/.tools/ailly/docs/20_PLAN.md new file mode 100644 index 00000000000..7036e4dcf49 --- /dev/null +++ b/.tools/ailly/docs/20_PLAN.md @@ -0,0 +1,19 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: > + Create a backronym using P L A N for iterating with Ailly. Be direct and + + professional, not poetic or whimsical. This backronym will summarize Ailly's + + project and workflow, so it needs to be approachable marketing copy. Do not include "Ailly" in any item. + Prefer concrete actions, or nouns. +--- + +I have thought about these instructions. diff --git a/.tools/ailly/docs/21a_PLAN_P.md b/.tools/ailly/docs/21a_PLAN_P.md new file mode 100644 index 00000000000..1d68c5826e1 --- /dev/null +++ b/.tools/ailly/docs/21a_PLAN_P.md @@ -0,0 +1,20 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +skip: true +prompt: Provide three alternatives for the P letter. +--- + +Here are three alternatives for the P in the P.L.A.N. backronym for iterating with Ailly: + +1. Prepare a precise prompt +2. Pose a purposeful prompt +3. Propel your project with prompts + +Prepare a purposeful prompt diff --git a/.tools/ailly/docs/21b_PLAN_L.md b/.tools/ailly/docs/21b_PLAN_L.md new file mode 100644 index 00000000000..eab5978fb23 --- /dev/null +++ b/.tools/ailly/docs/21b_PLAN_L.md @@ -0,0 +1,19 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: Provide three alternatives for the L letter. +--- + +Here are three alternatives for the L in the P.L.A.N. backronym: + +1. Leverage language models +2. Let the model learn +3. Loop through iterations + +Leverage language models diff --git a/.tools/ailly/docs/21c_PLAN_A.md b/.tools/ailly/docs/21c_PLAN_A.md new file mode 100644 index 00000000000..7951070ebd5 --- /dev/null +++ b/.tools/ailly/docs/21c_PLAN_A.md @@ -0,0 +1,20 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +skip: true +prompt: Provide three alternatives for the A letter. +--- + +Here are three alternatives for the A in the P.L.A.N. backronym for iterating with Ailly: + +1. Assess the generated content +2. Analyze Ailly's response +3. Appraise the draft thoroughly + +Assess the generated content diff --git a/.tools/ailly/docs/21d_PLAN_N.md b/.tools/ailly/docs/21d_PLAN_N.md new file mode 100644 index 00000000000..727634c8bac --- /dev/null +++ b/.tools/ailly/docs/21d_PLAN_N.md @@ -0,0 +1,20 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +skip: true +prompt: Provide three alternatives for the N letter. +--- + +Here are three alternatives for the N in the P.L.A.N. backronym for iterating with Ailly: + +1. Narrow your context iteratively +2. Nurture the process incrementally +3. Navigate revisions methodically + +Narrow your context iteratively diff --git a/workflows/sesv2_weekly_mailer/content/.aillyrc b/workflows/sesv2_weekly_mailer/content/.aillyrc new file mode 100644 index 00000000000..85f4b0adfe6 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/.aillyrc @@ -0,0 +1,53 @@ +--- +combined: true +overwrite: false +--- + +TCX SDK Code Examples +The TCX SDK Code Examples team produces code examples that demonstrate how to automate AWS services to accomplish key user stories for developers and programmers. These code examples are quick and easy to find and use, are continually tested, and demonstrate AWS and community coding best practices. + +Mission + +We provide code examples for builders integrating AWS services into their applications and business workflows using the AWS Software Development Kits (SDKs). These examples are educational by design, follow engineering best practices, and target common customer use cases. Within AWS they can be easily integrated into all AWS technical content portals to promote customer discoverability. + +Vision + +We envision a best-in-class library of code examples for every AWS service and in every actively maintained SDK language. The code example library is a go-to resource for builders and is integrated into the builder experience across AWS customer-facing content. Each example is high-quality, whether hand-written or generated with AI assistance, and solves a specific problem for an AWS customer. + +Tenets + +These are our tenets, unless you know better ones: + +We are educators. Comprehension and learnability always take precedence. +We are engineers. Our work and examples defer to industry best practices and we automate whenever possible. +Our examples address common user challenges. They do not deliberately mirror AWS service silos. +Our examples are discoverable. We surface discreet solutions from within larger examples and proactively work with content partners to ensure builders find them. +We are subject matter experts. We are the primary reference for code example standards in TCX. + +A Workflow Example, as defined by the TCX Code Examples team, is an example scenario that is targeted to a particular real-world user story, use case, problem, or other common service integration. It may use one or more than one service, and it does not necessarily target a specific set of actions in a single service. Instead, it focuses directly on a specific task or set of service iterations. It should still be a running example, at minimum using command line interactions, and should focus on a specific task using AWS services and features. + +What is Amazon SES? +Amazon Simple Email Service (SES) is an email platform that provides an easy, cost-effective way for you to send and receive email using your own email addresses and domains. + +For example, you can send marketing emails such as special offers, transactional emails such as order confirmations, and other types of correspondence such as newsletters. When you use Amazon SES to receive mail, you can develop software solutions such as email autoresponders, email unsubscribe systems, and applications that generate customer support tickets from incoming emails. + +You are a TCX code examples software engineer building a Workflow for Amazon SES v2. The programming language is Python. + +When you respond, do not include analysis or explanation. Do not wrap markdown in backtick code fences. Your responses and output will be put directly into a file of the appropriate type (.md, .py, or .sh). + +Workflow: A coupon newsletter. + +1. The builder will prepare the application. This application will have a verified email identity for the “send/reply” email addresses (CreateEmailIdentity), and a contact list for the newsletter’s recipients (CreateContactList). +2. A newsletter reader will sign up, sharing their email address (CreateContact). They will receive a welcome email (SendEmail, Simple). The workflow will repeat this step three times, gathering different email addresses for the newsletter. +3. The newsletter will have a template for several coupons (CreateTemplate). The newsletter will take five coupons, retrieve the contact list recipients (ListContacts), and send individual emails to each recipient (SendEmail Template). +4. Newsletter managers can then review dashboards in the console. + +SESv2 API actions used: + +- CreateContact +- CreateContactList +- CreateEmailIdentity +- CreateEmailTemplate +- ListContacts +- SendEmail using Simple format emails +- SendEmail using Template format emails diff --git a/workflows/sesv2_weekly_mailer/content/01_README.md b/workflows/sesv2_weekly_mailer/content/01_README.md new file mode 100644 index 00000000000..d989b83091d --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/01_README.md @@ -0,0 +1,84 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: > + Write the README.md for the workflow. The readme is independent of any + specific language, and should be written generically to allow a programmer + from any language to understand what the workflow will do, without needing to + know Python for the implementation. +--- + +# Amazon SES v2 Coupon Newsletter Workflow + +This workflow demonstrates how to use the Amazon Simple Email Service (SES) v2 to send a coupon newsletter to a list of contacts. It covers the following key steps: + +1. **Prepare the Application** + + - Create a verified email identity for the "send/reply" email addresses. + - Create a contact list to store the newsletter subscribers. + +2. **Gather Subscriber Email Addresses** + + - Allow subscribers to sign up for the newsletter by providing their email addresses. + - Send a welcome email to each new subscriber. + +3. **Send the Coupon Newsletter** + + - Create a template for the coupon newsletter. + - Retrieve the list of contacts (subscribers). + - Send individual emails with the coupon newsletter template to each subscriber. + - Include Unsubscribe links and headers to follow bulk email best practices. + +4. **Monitor and Review** + + - Review dashboards and metrics in the AWS console for the newsletter campaign. + +5. **Clean up** + + - Delete the template. + - Delete the contact list. + - Optionally delete the sender verified email identity. + +## Prerequisites + +Before running this workflow, ensure you have: + +- An AWS account with proper permissions to use Amazon SES v2. +- A verified email identity (domain or email address) in Amazon SES. + +## AWS Services Used + +This workflow uses the following AWS services: + +- Amazon Simple Email Service (SES) v2 + +## SES v2 Actions Used + +The workflow covers the following SES v2 API actions: + +- [`CreateContact`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContact.html) +- [`CreateContactList`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContactList.html) +- [`CreateEmailIdentity`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailIdentity.html) +- [`CreateEmailTemplate`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailTemplate.html) +- [`DeleteContactList`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteContactList.html) +- [`DeleteEmailTemplate`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteEmailTemplate.html) +- [`DeleteEmailIdentity`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteEmailIdentity.html) +- [`ListContacts`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListContacts.html) +- [`SendEmail`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html) (with both Simple and Template formats) + +## Implementations + +This example is implemented in the following languages: + +- [Python](../../python/example_code/sesv2/scenarios/wkflw-sesv2-mailer/README.md) + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 diff --git a/workflows/sesv2_weekly_mailer/content/10_CreateContact.md b/workflows/sesv2_weekly_mailer/content/10_CreateContact.md new file mode 100644 index 00000000000..07c812216a5 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/10_CreateContact.md @@ -0,0 +1,127 @@ +--- +skip: true +--- +****** CreateContact ****** +Creates a contact, which is an end-user who is receiving the email, and adds +them to a contact list. +***** Request Syntax ***** + + + + + + POST /v2/email/contact-lists/ + + ContactListName + + /contacts HTTP/1.1 +Content-type: application/json + + { + + " + +____AttributesData +___ + ": " + + string + + ", + " + +____EmailAddress +___ + ": " + + string + + ", + " + +____TopicPreferences +___ + ": [ + + { + + " + +____SubscriptionStatus +___ + ": " + + string + + ", + " + +____TopicName +___ + ": " + + string + + " + } + ], + " + +____UnsubscribeAll +___ + ": + + boolean + + } + +***** URI Request Parameters ***** +The request uses the following URI parameters. + ContactListName + The name of the contact list to which the contact should be added. + Required: Yes +***** Request Body ***** +The request accepts the following data in JSON format. + AttributesData + The attribute data attached to a contact. + Type: String + Required: No + EmailAddress + The contact's email address. + Type: String + Required: Yes + TopicPreferences + The contact's preferences for being opted-in to or opted-out of topics. + Type: Array of TopicPreference_objects + Required: No + UnsubscribeAll + A boolean value status noting if the contact is unsubscribed from all + contact list topics. + Type: Boolean + Required: No +***** Response Syntax ***** + + + + + + HTTP/1.1 200 + +***** Response Elements ***** +If the action is successful, the service sends back an HTTP 200 response with +an empty HTTP body. +***** Errors ***** +For information about the errors that are common to all actions, see Common +Errors_. + AlreadyExistsException + The resource specified in your request already exists. + HTTP Status Code: 400 + BadRequestException + The input you provided is invalid. + HTTP Status Code: 400 + NotFoundException + The resource you attempted to access doesn't exist. + HTTP Status Code: 404 + TooManyRequestsException + Too many requests have been made to the operation. + HTTP Status Code: 429 diff --git a/workflows/sesv2_weekly_mailer/content/10_CreateContactList.md b/workflows/sesv2_weekly_mailer/content/10_CreateContactList.md new file mode 100644 index 00000000000..136bd777d9f --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/10_CreateContactList.md @@ -0,0 +1,157 @@ +--- +skip: true +--- +****** CreateContactList ****** +Creates a contact list. +***** Request Syntax ***** + + + + + + POST /v2/email/contact-lists HTTP/1.1 +Content-type: application/json + + { + + " + +____ContactListName +___ + ": " + + string + + ", + " + +____Description +___ + ": " + + string + + ", + " + +____Tags +___ + ": [ + + { + + " + +____Key +___ + ": " + + string + + ", + " + +____Value +___ + ": " + + string + + " + } + ], + " + +____Topics +___ + ": [ + + { + + " + +____DefaultSubscriptionStatus +___ + ": " + + string + + ", + " + +____Description +___ + ": " + + string + + ", + " + +____DisplayName +___ + ": " + + string + + ", + " + +____TopicName +___ + ": " + + string + + " + } + ] +} + +***** URI Request Parameters ***** +The request does not use any URI parameters. +***** Request Body ***** +The request accepts the following data in JSON format. + ContactListName + The name of the contact list. + Type: String + Required: Yes + Description + A description of what the contact list is about. + Type: String + Required: No + Tags + The tags associated with a contact list. + Type: Array of Tag_objects + Required: No + Topics + An interest group, theme, or label within a list. A contact list can have + multiple topics. + Type: Array of Topic_objects + Required: No +***** Response Syntax ***** + + + + + + HTTP/1.1 200 + +***** Response Elements ***** +If the action is successful, the service sends back an HTTP 200 response with +an empty HTTP body. +***** Errors ***** +For information about the errors that are common to all actions, see Common +Errors_. + AlreadyExistsException + The resource specified in your request already exists. + HTTP Status Code: 400 + BadRequestException + The input you provided is invalid. + HTTP Status Code: 400 + LimitExceededException + There are too many instances of the specified resource type. + HTTP Status Code: 400 + TooManyRequestsException + Too many requests have been made to the operation. + HTTP Status Code: 429 diff --git a/workflows/sesv2_weekly_mailer/content/10_CreateEmailIdentity.md b/workflows/sesv2_weekly_mailer/content/10_CreateEmailIdentity.md new file mode 100644 index 00000000000..41e79ad7fff --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/10_CreateEmailIdentity.md @@ -0,0 +1,311 @@ +--- +skip: true +--- +****** CreateEmailIdentity ****** +Starts the process of verifying an email identity. An identity is an email +address or domain that you use when you send email. Before you can use an +identity to send email, you first have to verify it. By verifying an identity, +you demonstrate that you're the owner of the identity, and that you've given +Amazon SES API v2 permission to send email from the identity. +When you verify an email address, Amazon SES sends an email to the address. +Your email address is verified as soon as you follow the link in the +verification email. +When you verify a domain without specifying the DkimSigningAttributes object, +this operation provides a set of DKIM tokens. You can convert these tokens into +CNAME records, which you then add to the DNS configuration for your domain. +Your domain is verified when Amazon SES detects these records in the DNS +configuration for your domain. This verification method is known as Easy_DKIM_. +Alternatively, you can perform the verification process by providing your own +public-private key pair. This verification method is known as Bring Your Own +DKIM (BYODKIM). To use BYODKIM, your call to the CreateEmailIdentity operation +has to include the DkimSigningAttributes object. When you specify this object, +you provide a selector (a component of the DNS record name that identifies the +public key to use for DKIM authentication) and a private key. +When you verify a domain, this operation provides a set of DKIM tokens, which +you can convert into CNAME tokens. You add these CNAME tokens to the DNS +configuration for your domain. Your domain is verified when Amazon SES detects +these records in the DNS configuration for your domain. For some DNS providers, +it can take 72 hours or more to complete the domain verification process. +Additionally, you can associate an existing configuration set with the email +identity that you're verifying. +***** Request Syntax ***** + + + + + + POST /v2/email/identities HTTP/1.1 +Content-type: application/json + + { + + " + +____ConfigurationSetName +___ + ": " + + string + + ", + " + +____DkimSigningAttributes +___ + ": + + { + + " + +____DomainSigningPrivateKey +___ + ": " + + string + + ", + " + +____DomainSigningSelector +___ + ": " + + string + + ", + " + +____NextSigningKeyLength +___ + ": " + + string + + " + }, + " + +____EmailIdentity +___ + ": " + + string + + ", + " + +____Tags +___ + ": [ + + { + + " + +____Key +___ + ": " + + string + + ", + " + +____Value +___ + ": " + + string + + " + } + ] +} + +***** URI Request Parameters ***** +The request does not use any URI parameters. +***** Request Body ***** +The request accepts the following data in JSON format. + ConfigurationSetName + The configuration set to use by default when sending from this identity. + Note that any configuration set defined in the email sending request + takes precedence. + Type: String + Required: No + DkimSigningAttributes + If your request includes this object, Amazon SES configures the identity + to use Bring Your Own DKIM (BYODKIM) for DKIM authentication purposes, + or, configures the key length to be used for Easy_DKIM_. + You can only specify this object if the email identity is a domain, as + opposed to an address. + Type: DkimSigningAttributes_object + Required: No + EmailIdentity + The email address or domain to verify. + Type: String + Length Constraints: Minimum length of 1. + Required: Yes + Tags + An array of objects that define the tags (keys and values) to associate + with the email identity. + Type: Array of Tag_objects + Required: No +***** Response Syntax ***** + + + + + + HTTP/1.1 200 +Content-type: application/json + + { + + " + +____DkimAttributes +___ + ": + + { + + " + +____CurrentSigningKeyLength +___ + ": " + + + string + + + ", + " + +____LastKeyGenerationTimestamp +___ + ": + + + number + + + , + " + +____NextSigningKeyLength +___ + ": " + + + string + + + ", + " + +____SigningAttributesOrigin +___ + ": " + + + string + + + ", + " + +____SigningEnabled +___ + ": + + + boolean + + + , + " + +____Status +___ + ": " + + + string + + + ", + " + +____Tokens +___ + ": [ " + + + string + + + " ] + }, + " + +____IdentityType +___ + ": " + + + string + + + ", + " + +____VerifiedForSendingStatus +___ + ": + + + boolean + + + } + +***** Response Elements ***** +If the action is successful, the service sends back an HTTP 200 response. +The following data is returned in JSON format by the service. + DkimAttributes + An object that contains information about the DKIM attributes for the + identity. + Type: DkimAttributes_object + IdentityType + The email identity type. Note: the MANAGED_DOMAIN identity type is not + supported. + Type: String + Valid Values: EMAIL_ADDRESS | DOMAIN | MANAGED_DOMAIN + VerifiedForSendingStatus + Specifies whether or not the identity is verified. You can only send + email from verified email addresses or domains. For more information + about verifying identities, see the Amazon_Pinpoint_User_Guide_. + Type: Boolean +***** Errors ***** +For information about the errors that are common to all actions, see Common +Errors_. + AlreadyExistsException + The resource specified in your request already exists. + HTTP Status Code: 400 + BadRequestException + The input you provided is invalid. + HTTP Status Code: 400 + ConcurrentModificationException + The resource is being modified by another operation or thread. + HTTP Status Code: 500 + LimitExceededException + There are too many instances of the specified resource type. + HTTP Status Code: 400 + NotFoundException + The resource you attempted to access doesn't exist. + HTTP Status Code: 404 + TooManyRequestsException + Too many requests have been made to the operation. + HTTP Status Code: 429 diff --git a/workflows/sesv2_weekly_mailer/content/10_CreateEmailTemplate.md b/workflows/sesv2_weekly_mailer/content/10_CreateEmailTemplate.md new file mode 100644 index 00000000000..91e492cf377 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/10_CreateEmailTemplate.md @@ -0,0 +1,106 @@ +--- +skip: true +--- +****** CreateEmailTemplate ****** +Creates an email template. Email templates enable you to send personalized +email to one or more destinations in a single API operation. For more +information, see the Amazon_SES_Developer_Guide_. +You can execute this operation no more than once per second. +***** Request Syntax ***** + + + + + + POST /v2/email/templates HTTP/1.1 +Content-type: application/json + + { + + " + +____TemplateContent +___ + ": + + { + + " + +____Html +___ + ": " + + string + + ", + " + +____Subject +___ + ": " + + string + + ", + " + +____Text +___ + ": " + + string + + " + }, + " + +____TemplateName +___ + ": " + + string + + " +} + +***** URI Request Parameters ***** +The request does not use any URI parameters. +***** Request Body ***** +The request accepts the following data in JSON format. + TemplateContent + The content of the email template, composed of a subject line, an HTML + part, and a text-only part. + Type: EmailTemplateContent_object + Required: Yes + TemplateName + The name of the template. + Type: String + Length Constraints: Minimum length of 1. + Required: Yes +***** Response Syntax ***** + + + + + + HTTP/1.1 200 + +***** Response Elements ***** +If the action is successful, the service sends back an HTTP 200 response with +an empty HTTP body. +***** Errors ***** +For information about the errors that are common to all actions, see Common +Errors_. + AlreadyExistsException + The resource specified in your request already exists. + HTTP Status Code: 400 + BadRequestException + The input you provided is invalid. + HTTP Status Code: 400 + LimitExceededException + There are too many instances of the specified resource type. + HTTP Status Code: 400 + TooManyRequestsException + Too many requests have been made to the operation. + HTTP Status Code: 429 diff --git a/workflows/sesv2_weekly_mailer/content/10_ListContacts.md b/workflows/sesv2_weekly_mailer/content/10_ListContacts.md new file mode 100644 index 00000000000..86d813b7885 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/10_ListContacts.md @@ -0,0 +1,251 @@ +--- +skip: true +--- +****** ListContacts ****** +Lists the contacts present in a specific contact list. +***** Request Syntax ***** + + + + + + GET /v2/email/contact-lists/ + + ContactListName + + /contacts?NextToken= + + NextToken + + &PageSize= + + PageSize + + HTTP/1.1 +Content-type: application/json + + { + + " + +____Filter +___ + ": + + { + + " + +____FilteredStatus +___ + ": " + + string + + ", + " + +____TopicFilter +___ + ": + + { + + " + +____TopicName +___ + ": " + + string + + ", + " + +____UseDefaultIfPreferenceUnavailable +___ + ": + + boolean + + } + } +} + +***** URI Request Parameters ***** +The request uses the following URI parameters. + ContactListName + The name of the contact list. + Required: Yes + NextToken + A string token indicating that there might be additional contacts + available to be listed. Use the token provided in the Response to use in + the subsequent call to ListContacts with the same parameters to retrieve + the next page of contacts. + PageSize + The number of contacts that may be returned at once, which is dependent + on if there are more or less contacts than the value of the PageSize. Use + this parameter to paginate results. If additional contacts exist beyond + the specified limit, the NextToken element is sent in the response. Use + the NextToken value in subsequent requests to retrieve additional + contacts. +***** Request Body ***** +The request accepts the following data in JSON format. + Filter + A filter that can be applied to a list of contacts. + Type: ListContactsFilter_object + Required: No +***** Response Syntax ***** + + + + + + HTTP/1.1 200 +Content-type: application/json + + { + + " + +____Contacts +___ + ": [ + + { + + " + +____EmailAddress +___ + ": " + + + string + + + ", + " + +____LastUpdatedTimestamp +___ + ": + + + number + + + , + " + +____TopicDefaultPreferences +___ + ": [ + + { + + " + +____SubscriptionStatus +___ + ": " + + + string + + + ", + " + +____TopicName +___ + ": " + + + string + + + " + } + ], + " + +____TopicPreferences +___ + ": [ + + { + + " + +____SubscriptionStatus +___ + ": " + + + string + + + ", + " + +____TopicName +___ + ": " + + + string + + + " + } + ], + " + +____UnsubscribeAll +___ + ": + + + boolean + + + } + ], + " + +____NextToken +___ + ": " + + + string + + + " +} + +***** Response Elements ***** +If the action is successful, the service sends back an HTTP 200 response. +The following data is returned in JSON format by the service. + Contacts + The contacts present in a specific contact list. + Type: Array of Contact_objects + NextToken + A string token indicating that there might be additional contacts + available to be listed. Copy this token to a subsequent call to + ListContacts with the same parameters to retrieve the next page of + contacts. + Type: String +***** Errors ***** +For information about the errors that are common to all actions, see Common +Errors_. + BadRequestException + The input you provided is invalid. + HTTP Status Code: 400 + NotFoundException + The resource you attempted to access doesn't exist. + HTTP Status Code: 404 + TooManyRequestsException + Too many requests have been made to the operation. + HTTP Status Code: 429 diff --git a/workflows/sesv2_weekly_mailer/content/10_SendEmail.md b/workflows/sesv2_weekly_mailer/content/10_SendEmail.md new file mode 100644 index 00000000000..08a06b9ee08 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/10_SendEmail.md @@ -0,0 +1,532 @@ +--- +skip: true +--- +****** SendEmail ****** +Sends an email message. You can use the Amazon SES API v2 to send the following +types of messages: + * Simple – A standard email message. When you create this type of message, + you specify the sender, the recipient, and the message body, and Amazon + SES assembles the message for you. + * Raw – A raw, MIME-formatted email message. When you send this type of + email, you have to specify all of the message headers, as well as the + message body. You can use this message type to send messages that contain + attachments. The message that you specify has to be a valid MIME message. + * Templated – A message that contains personalization tags. When you send + this type of email, Amazon SES API v2 automatically replaces the tags + with values that you specify. +***** Request Syntax ***** + + + + + + POST /v2/email/outbound-emails HTTP/1.1 +Content-type: application/json + + { + + " + +____ConfigurationSetName +___ + ": " + + string + + ", + " + +____Content +___ + ": + + { + + " + +____Raw +___ + ": + + { + + " + +____Data +___ + ": + + blob + + }, + " + +____Simple +___ + ": + + { + + " + +____Body +___ + ": + + { + + " + +____Html +___ + ": + + { + + " + +____Charset +___ + ": " + + string + + ", + " + +____Data +___ + ": " + + string + + " + }, + " + +____Text +___ + ": + + { + + " + +____Charset +___ + ": " + + string + + ", + " + +____Data +___ + ": " + + string + + " + } + }, + " + +____Headers +___ + ": [ + + { + + " + +____Name +___ + ": " + + string + + ", + " + +____Value +___ + ": " + + string + + " + } + ], + " + +____Subject +___ + ": + + { + + " + +____Charset +___ + ": " + + string + + ", + " + +____Data +___ + ": " + + string + + " + } + }, + " + +____Template +___ + ": + + { + + " + +____Headers +___ + ": [ + + { + + " + +____Name +___ + ": " + + string + + ", + " + +____Value +___ + ": " + + string + + " + } + ], + " + +____TemplateArn +___ + ": " + + string + + ", + " + +____TemplateData +___ + ": " + + string + + ", + " + +____TemplateName +___ + ": " + + string + + " + } + }, + " + +____Destination +___ + ": + + { + + " + +____BccAddresses +___ + ": [ " + + string + + " ], + " + +____CcAddresses +___ + ": [ " + + string + + " ], + " + +____ToAddresses +___ + ": [ " + + string + + " ] + }, + " + +____EmailTags +___ + ": [ + + { + + " + +____Name +___ + ": " + + string + + ", + " + +____Value +___ + ": " + + string + + " + } + ], + " + +____FeedbackForwardingEmailAddress +___ + ": " + + string + + ", + " + +____FeedbackForwardingEmailAddressIdentityArn +___ + ": " + + string + + ", + " + +____FromEmailAddress +___ + ": " + + string + + ", + " + +____FromEmailAddressIdentityArn +___ + ": " + + string + + ", + " + +____ListManagementOptions +___ + ": + + { + + " + +____ContactListName +___ + ": " + + string + + ", + " + +____TopicName +___ + ": " + + string + + " + }, + " + +____ReplyToAddresses +___ + ": [ " + + string + + " ] +} + +***** URI Request Parameters ***** +The request does not use any URI parameters. +***** Request Body ***** +The request accepts the following data in JSON format. + ConfigurationSetName + The name of the configuration set to use when sending the email. + Type: String + Required: No + Content + An object that contains the body of the message. You can send either a + Simple message, Raw message, or a Templated message. + Type: EmailContent_object + Required: Yes + Destination + An object that contains the recipients of the email message. + Type: Destination_object + Required: No + EmailTags + A list of tags, in the form of name/value pairs, to apply to an email + that you send using the SendEmail operation. Tags correspond to + characteristics of the email that you define, so that you can publish + email sending events. + Type: Array of MessageTag_objects + Required: No + FeedbackForwardingEmailAddress + The address that you want bounce and complaint notifications to be sent + to. + Type: String + Required: No + FeedbackForwardingEmailAddressIdentityArn + This parameter is used only for sending authorization. It is the ARN of + the identity that is associated with the sending authorization policy + that permits you to use the email address specified in the + FeedbackForwardingEmailAddress parameter. + For example, if the owner of example.com (which has ARN arn:aws:ses:us- + east-1:123456789012:identity/example.com) attaches a policy to it that + authorizes you to use feedback@example.com, then you would specify the + FeedbackForwardingEmailAddressIdentityArn to be arn:aws:ses:us-east-1: + 123456789012:identity/example.com, and the FeedbackForwardingEmailAddress + to be feedback@example.com. + For more information about sending authorization, see the Amazon_SES + Developer_Guide_. + Type: String + Required: No + FromEmailAddress + The email address to use as the "From" address for the email. The address + that you specify has to be verified. + Type: String + Required: No + FromEmailAddressIdentityArn + This parameter is used only for sending authorization. It is the ARN of + the identity that is associated with the sending authorization policy + that permits you to use the email address specified in the + FromEmailAddress parameter. + For example, if the owner of example.com (which has ARN arn:aws:ses:us- + east-1:123456789012:identity/example.com) attaches a policy to it that + authorizes you to use sender@example.com, then you would specify the + FromEmailAddressIdentityArn to be arn:aws:ses:us-east-1:123456789012: + identity/example.com, and the FromEmailAddress to be sender@example.com. + For more information about sending authorization, see the Amazon_SES + Developer_Guide_. + For Raw emails, the FromEmailAddressIdentityArn value overrides the X- + SES-SOURCE-ARN and X-SES-FROM-ARN headers specified in raw email message + content. + Type: String + Required: No + ListManagementOptions + An object used to specify a list or topic to which an email belongs, + which will be used when a contact chooses to unsubscribe. + Type: ListManagementOptions_object + Required: No + ReplyToAddresses + The "Reply-to" email addresses for the message. When the recipient + replies to the message, each Reply-to address receives the reply. + Type: Array of strings + Required: No +***** Response Syntax ***** + + + + + + HTTP/1.1 200 +Content-type: application/json + + { + + " + +____MessageId +___ + ": " + + + string + + + " +} + +***** Response Elements ***** +If the action is successful, the service sends back an HTTP 200 response. +The following data is returned in JSON format by the service. + MessageId + A unique identifier for the message that is generated when the message is + accepted. + * Note * + It's possible for Amazon SES to accept a message without sending it. For + example, this can happen when the message that you're trying to send has + an attachment that contains a virus, or when you send a templated email + that contains invalid personalization content. + Type: String +***** Errors ***** +For information about the errors that are common to all actions, see Common +Errors_. + AccountSuspendedException + The message can't be sent because the account's ability to send email has + been permanently restricted. + HTTP Status Code: 400 + BadRequestException + The input you provided is invalid. + HTTP Status Code: 400 + LimitExceededException + There are too many instances of the specified resource type. + HTTP Status Code: 400 + MailFromDomainNotVerifiedException + The message can't be sent because the sending domain isn't verified. + HTTP Status Code: 400 + MessageRejected + The message can't be sent because it contains invalid content. + HTTP Status Code: 400 + NotFoundException + The resource you attempted to access doesn't exist. + HTTP Status Code: 404 + SendingPausedException + The message can't be sent because the account's ability to send email is + currently paused. + HTTP Status Code: 400 + TooManyRequestsException + Too many requests have been made to the operation. + HTTP Status Code: 429 diff --git a/workflows/sesv2_weekly_mailer/content/30_SPECIFICATION.md b/workflows/sesv2_weekly_mailer/content/30_SPECIFICATION.md new file mode 100644 index 00000000000..e10a7603a00 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/30_SPECIFICATION.md @@ -0,0 +1,212 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: > + Write the SPECIFICATION.md for the workflow. + + + The specification is independent of any programming language, and should + enable any programmer competent with programming using any published AWS SDK + to follow along. It must specify the API calls to make, and it must include + the parameters to send. It should describe the parameters in a list format. + Implementations will use the specific SDKs, so it does + not need to specify URL calls to make, only the API calls and the request + parameters to include. It must specify the exact environment variable names + and files to use when referring to runtime data. + + + Additional instructions for each section of the specification: + + + Prepare the Application: + + - Print a short intro message. + + - The verified email should be provided by the builder in an environment + variable for the workflow. + + - The contact list should have a hardcoded name relevant to the workflow. + + - The newsletter should use a provided template, but read it in at runtime. + + + Gather Subscriber Email Addresses: + + + - The application should ask the user for several (3 to 5) email addresses + interactively on the command line. + + - Suggest that during testing, users use a single email address and provide a + number of plus addresses. + + - The contents of the welcome email should be provided in the spec, but read + from an html file at runtime. + + + Send the Coupon Newsletter: + + + - After gathering the subscriber emails, send a weekly newsletter. + + - Specify six coupon items for the newsletter. + + - Include links to the template reference documentation, + https://docs.aws.amazon.com/ses/latest/dg/send-personalized-email-api.html + + + Monitor and Review: + + + - Leave this section vague for now, we will fill it in later. +--- + +# SES v2 Coupon Newsletter Workflow Specification + +Use the Amazon Simple Email Service (SES) v2 API to manage a subscription list for a weekly newsletter. + +At the start of the workflow, print this header: + +> Welcome to the Amazon SES v2 Coupon Newsletter Workflow! +> +> This workflow will help you: +> +> 1. Prepare a verified email identity and contact list for your newsletter. +> 2. Gather subscriber email addresses and send them a welcome email. +> 3. Send a weekly coupon newsletter to your subscribers using email templates. +> 4. Monitor your sending activity and metrics in the AWS console. +> +> Let's get started! + +## Prepare the Application + +1. Create an email identity. + - Request a `verified email` address from the user. This will be used as the `from` address, and the user will need to click a verification link in their email before continuing to part 3. + - Operation: **CreateEmailIdentity** + - Parameters: + - `EmailIdentity`: Value of the `verified email` given by the user. + - Errors: + - `AlreadyExistsException`: If the identity already exists, skip this step and proceed with the next operation. This error can be safely ignored. + - `NotFoundException`: If the identity does not exist, fail the workflow and inform the user that the provided email address is not verified. + - `LimitExceededException`: If the limit for email identities is exceeded, fail the workflow and inform the user that they have reached the limit for email identities. +2. Create a contact list with the name `weekly-coupons-newsletter`. + - Operation: **CreateContactList** + - Parameters: + - `ContactListName`: `weekly-coupons-newsletter` + - Errors: + - `AlreadyExistsException`: If the contact list already exists, skip this step and proceed with the next operation. This error can be safely ignored. + - `LimitExceededException`: If the limit for contact lists is exceeded, fail the workflow and inform the user that they have reached the limit for contact lists. + +## Gather Subscriber Email Addresses + +1. Prompt the user to enter a base email address for subscribing to the newsletter. + - For testing purposes, this workflow uses a single email address with [subaddress extensions](https://www.rfc-editor.org/rfc/rfc5233.html) (e.g., `user+ses-weekly-newsletter-1@example.com`, `user+ses-weekly-newsletter-2@example.com`, etc., also known as [plus addressing](https://en.wikipedia.org/wiki/Email_address#:~:text=For%20example%2C%20the%20address%20joeuser,sorting%2C%20and%20for%20spam%20control.)). + - Create 3 variants of this email address as `{user email}+ses-weekly-newsletter-{i}@{user domain}`. + - `{user-email}` is the portion up to the first `@` (0x40, dec 64). The `{user domain}` is everything after the first `@`. +2. For each email address created: + 1. Create a new contact with the provided email address in the `weekly-coupons-newsletter` contact list. + - Operation: **CreateContact** + - Parameters: + - `ContactListName`: `weekly-coupons-newsletter` + - `EmailAddress`: The email address provided by the user. + - Errors: + - `AlreadyExistsException`: If the contact already exists, skip this step for that contact and proceed with the next contact. This error can be safely ignored. + 2. Send a welcome email to the new contact using the content from the `welcome.html` file. + - Operation: **SendEmail** + - Parameters: + - `FromEmailAddress`: Use the `verified_email` address provided in Prepare the Application. + - `Destination.ToAddresses`: The email address provided by the user. + - `Content.Simple.Subject.Data`: "Welcome to the Weekly Coupons Newsletter" + - `Content.Simple.Body.Text.Data`: Read the content from the `welcome.txt` file. + - `Content.Simple.Body.Html.Data`: Read the content from the `welcome.html` file. + - Errors: + - See Errors in `SendEmail` for "Send the Coupon Newsletter" + +## Send the Coupon Newsletter + +1. Create an email template named `weekly-coupons` with the following content: + - Subject: `Weekly Coupons Newsletter` + - HTML Content: Available in the `coupon-newsletter.html` file. + - Text Content: Available in the `coupon-newsletter.txt` file. + - The emails should include an [Unsubscribe](#) link, using the url `{{amazonSESUnsubscribeUrl}}`. + - Operation: **CreateEmailTemplate** + - Parameters: + - `TemplateName`: `weekly-coupons` + - `TemplateContent`: + - `Subject`: `Weekly Coupons Newsletter` + - `Html`: Read from the `coupon-newsletter.html` file + - `Text`: Read from the `coupon-newsletter.txt` file + - Errors: + - `AlreadyExistsException`: If the template already exists, skip this step and proceed with the next operation. This error can be safely ignored. + - `LimitExceededException`: If the limit for email templates is exceeded, fail the workflow and inform the user that they have reached the limit for email templates. +2. Retrieve the list of contacts from the `weekly-coupons-newsletter` contact list. + - Operation: **ListContacts** + - Parameters: + - `ContactListName`: `weekly-coupons-newsletter` + - Errors: + - `NotFoundException`: If the contact list does not exist, fail the workflow and inform the user that the contact list is missing. +3. Send an email using the `weekly-coupons` template to each contact in the list. + - The email should include the following coupon items: + 1. 20% off on all electronics + 2. Buy one, get one free on books + 3. 15% off on home appliances + 4. Free shipping on orders over $50 + 5. 25% off on outdoor gear + 6. 10% off on groceries + - Operation: **SendEmail** + - Parameters: + - `Destination`: + - `ToAddresses`: One email address from the `ListContacts` response (each email address must get a unique `SendEmail` call for tracking and unsubscribe purposes). + - `Content`: + - `Template`: + - `TemplateName`: `weekly-coupons` + - `TemplateData`: JSON string representing an object with one key, `coupons`, which is an array of coupon items. Each coupon entry in the array should have one key, `details`, with the details of the coupon. See `sample_coupons.json`. + - `FromEmailAddress`: (Use the verified email address from step 1) + - `ListManagementOptions`: + - `ContactListName`: `weekly-coupons-newsletter` to correctly populate Unsubscribe headers and the `{{amazonSESUnsubscribeUrl}}` value. + - Errors: + - `AccountSuspendedException`: If the account is suspended, fail the workflow and inform the user that their account is suspended. + - `MailFromDomainNotVerifiedException`: If the sending domain is not verified, fail the workflow and inform the user that the sending domain is not verified. + - `MessageRejected`: If the message is rejected due to invalid content, fail the workflow and inform the user that the message content is invalid. + - `SendingPausedException`: If sending is paused, fail the workflow and inform the user that sending is currently paused for their account. + +For more information on using templates with SES v2, refer to the [Amazon SES Developer Guide](https://docs.aws.amazon.com/ses/latest/dg/send-personalized-email-api.html). + +## Monitor and Review + +1. [Monitor your sending activity](https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html) using the [SES Homepage](https://console.aws.amazon.com/ses/home#/account) in the AWS console. + +## Clean up + +1. Delete the contact list. This operation also deletes all contacts in the list, without needing separate calls. + - Operation: **DeleteContactList** + - Parameters: + - `ContactListName`: `weekly-coupons-newsletter` + - `NotFoundException`: If the contact list does not exist, skip this step and proceed with the next operation. This error can be safely ignored. + - Errors: + +- `NotFoundException`: If the contact list does not exist, skip this step and proceed with the next operation. This error can be safely ignored. + +2. Delete the template. + - Operation: **DeleteEmailTemplate** + - Parameters: + - `TemplateName`: `weekly-coupons` + - `NotFoundException`: If the email template does not exist, skip this step and proceed with the next operation. This error can be safely ignored. + - Errors: + - `NotFoundException`: If the email template does not exist, skip this step and proceed with the next operation. This error can be safely ignored. +3. Delete the email identity (optional). Ask the user before performing this step, as they may not want to re-verify the email identity. + - Operation: **DeleteEmailIdentity** + - Parameters: + - `EmailIdentity`: Value of the `verified email` given by the user in part 1. + - Errors: + - `NotFoundException`: If the email identity does not exist, skip this step and proceed with the next operation. This error can be safely ignored. + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 diff --git a/workflows/sesv2_weekly_mailer/content/31_SPEC_PREP.md b/workflows/sesv2_weekly_mailer/content/31_SPEC_PREP.md new file mode 100644 index 00000000000..6767c1a67b1 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/31_SPEC_PREP.md @@ -0,0 +1,37 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: | + Describe the exact SESv2 API calls and parameters for this step. + ## Prepare the Application +--- + +For the "Prepare the Application" step, the following SES v2 API calls and parameters should be used: + +1. **CreateEmailIdentity** + + Parameters: + + - `EmailIdentity`: Value of the `verified email` given by the user. + +2. **CreateContactList** + + Parameters: + + - `ContactListName`: `weekly-coupons-newsletter` + +3. Create an email template named `weekly-coupons` with the following content: + + - API Call: `CreateEmailTemplate` + - Parameters: + - `TemplateName`: `weekly-coupons` + - `TemplateContent`: + - `Subject`: `Weekly Coupons Newsletter` + - `Html`: Read from the `coupon-newsletter.html` file + - `Text`: Read from the `coupon-newsletter.txt` file diff --git a/workflows/sesv2_weekly_mailer/content/31a_intro_message.md b/workflows/sesv2_weekly_mailer/content/31a_intro_message.md new file mode 100644 index 00000000000..1945eb9d69e --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/31a_intro_message.md @@ -0,0 +1,27 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: >- + Create a short intro message for the workflow, that will print at the + beginning of running it. +--- + +Here's a short intro message for the SES v2 Coupon Newsletter Workflow: + +``` +Welcome to the Amazon SES v2 Coupon Newsletter Workflow! + +This workflow will help you: +1. Prepare a verified email identity and contact list for your newsletter. +2. Gather subscriber email addresses and send them a welcome email. +3. Send a weekly coupon newsletter to your subscribers using email templates. +4. Monitor your sending activity and metrics in the AWS console. + +Let's get started! +``` diff --git a/workflows/sesv2_weekly_mailer/content/32_SPEC_GATHER.md b/workflows/sesv2_weekly_mailer/content/32_SPEC_GATHER.md new file mode 100644 index 00000000000..b69605b0d34 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/32_SPEC_GATHER.md @@ -0,0 +1,35 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: | + Describe the exact SESv2 API calls and parameters for this step. + + ## Gather Subscriber Email Addresses +--- + +For the "Gather Subscriber Email Addresses" step, the following SES v2 API calls and parameters should be used: + +1. **CreateContact** + + - `ContactListName`: `weekly-coupons-newsletter` + - `EmailAddress`: The email address provided by the user. + +2. **SendEmail** + - `FromEmailAddress`: Use the `verified_email` address provided in Prepare the Application. + - `Destination.ToAddresses`: The email address provided by the user. + - `Content.Simple.Subject.Data`: "Welcome to the Weekly Coupons Newsletter" + - `Content.Simple.Body.Html.Data`: Read the content from the `welcome.html` file. + +For each email address provided by the user: + +1. Use the `CreateContact` call to create a new contact in the `weekly-coupons-newsletter` contact list with the provided email address. + +2. Use the `SendEmail` call to send a welcome email to the new contact. The `FromEmailAddress` should be the verified email address provided by the user when preparing the application. The `Destination.ToAddresses` should be set to the email address provided by the user. The email subject should be "Welcome to the Weekly Coupons Newsletter", and the HTML content of the email should be read from the `welcome.html` file. + +Repeat these two steps for each email address provided by the user. diff --git a/workflows/sesv2_weekly_mailer/content/33_SPEC_SEND.md b/workflows/sesv2_weekly_mailer/content/33_SPEC_SEND.md new file mode 100644 index 00000000000..faf896c2a5a --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/33_SPEC_SEND.md @@ -0,0 +1,38 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: > + Describe the exact SESv2 API calls and parameters for this step: + + ## Send the Coupon Newsletter + + The sample JSON should come from a file, `sample_coupons.json`. +--- + +## Send the Coupon Newsletter + +1. Retrieve the list of contacts from the `weekly-coupons-newsletter` contact list. + + - API Call: `ListContacts` + - Parameters: + - `ContactListName`: `weekly-coupons-newsletter` + +2. Send an email using the `weekly-coupons` template to each contact in the list. + + - API Call: `SendEmail` + - Parameters: + - `Destination`: + - `ToAddresses`: (List of email addresses from the `ListContacts` response) + - `Content`: + - `Template`: + - `TemplateName`: `weekly-coupons` + - `TemplateData`: JSON string representing an object with one key, `coupons`, which is an array of coupon items. Each coupon entry in the array should have one key, `details`, with the details of the coupon. See `sample_coupons.json`. + - `FromEmailAddress`: (Use the verified email address) + +For more information on using templates with SES v2, refer to the official documentation: https://docs.aws.amazon.com/ses/latest/dg/send-personalized-email-api.html diff --git a/workflows/sesv2_weekly_mailer/content/34_SPEC_CLEANUP.md b/workflows/sesv2_weekly_mailer/content/34_SPEC_CLEANUP.md new file mode 100644 index 00000000000..980f7916781 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/34_SPEC_CLEANUP.md @@ -0,0 +1,17 @@ +--- +prompt: | + Describe the exact SESv2 API calls and parameters for this step. + ## Clean Up +--- + +## Clean up + +1. Delete the contact list. This operation also deletes all contacts in the list, without needing separate calls. + - Operation: **DeleteContactList** + - `ContactListName`: `weekly-coupons-newsletter` +2. Delete the template. + - Operation: **DeleteEmailTemplate** + - `TemplateName`: `weekly-coupons` +3. Delete the email identity (optional). Ask the user before performing this step, as they may not want to re-verify the email identity. + - Operation: **DeleteEmailIdentity** + - `EmailIdentity`: Value of the `verified email` given by the user in part 1. diff --git a/workflows/sesv2_weekly_mailer/content/38_SPEC_ERRORS.md b/workflows/sesv2_weekly_mailer/content/38_SPEC_ERRORS.md new file mode 100644 index 00000000000..293d8859eec --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/38_SPEC_ERRORS.md @@ -0,0 +1,71 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: > + For each operation across all spec calls, describe necessary API error + handling - which errors should be handled and recovered from, and which should + be passed to the end user? + + Do not consider common SDK exceptions, like TooManyRequestsException or + BadRequestException. + + Format your output using the same section titles as the original spec. The + answers will be copied into the original spec as given. +--- + +## Prepare the Application + +**CreateEmailIdentity** + +- `AlreadyExistsException`: If the identity already exists, skip this step and proceed with the next operation. This error can be safely ignored. +- `NotFoundException`: If the identity does not exist, fail the workflow and inform the user that the provided email address is not verified. +- `LimitExceededException`: If the limit for email identities is exceeded, fail the workflow and inform the user that they have reached the limit for email identities. + +**CreateContactList** + +- `AlreadyExistsException`: If the contact list already exists, skip this step and proceed with the next operation. This error can be safely ignored. +- `LimitExceededException`: If the limit for contact lists is exceeded, fail the workflow and inform the user that they have reached the limit for contact lists. + +## Gather Subscriber Email Addresses + +**CreateContact** + +- `AlreadyExistsException`: If the contact already exists, skip this step for that contact and proceed with the next contact. This error can be safely ignored. + +## Send the Coupon Newsletter + +**CreateEmailTemplate** + +- `AlreadyExistsException`: If the template already exists, skip this step and proceed with the next operation. This error can be safely ignored. +- `LimitExceededException`: If the limit for email templates is exceeded, fail the workflow and inform the user that they have reached the limit for email templates. + +**ListContacts** + +- `NotFoundException`: If the contact list does not exist, fail the workflow and inform the user that the contact list is missing. + +**SendEmail** + +- `AccountSuspendedException`: If the account is suspended, fail the workflow and inform the user that their account is suspended. +- `MailFromDomainNotVerifiedException`: If the sending domain is not verified, fail the workflow and inform the user that the sending domain is not verified. +- `MessageRejected`: If the message is rejected due to invalid content, fail the workflow and inform the user that the message content is invalid. +- `SendingPausedException`: If sending is paused, fail the workflow and inform the user that sending is currently paused for their account. + +## Clean Up + +**DeleteContactList** + +- `NotFoundException`: If the contact list does not exist, skip this step and proceed with the next operation. This error can be safely ignored. + +**DeleteEmailTemplate** + +- `NotFoundException`: If the email template does not exist, skip this step and proceed with the next operation. This error can be safely ignored. + +**DeleteEmailIdentity** + +- `NotFoundException`: If the email identity does not exist, skip this step and proceed with the next operation. This error can be safely ignored. diff --git a/workflows/sesv2_weekly_mailer/content/40_sample_files.md b/workflows/sesv2_weekly_mailer/content/40_sample_files.md new file mode 100644 index 00000000000..ba92853a9e7 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/40_sample_files.md @@ -0,0 +1,47 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: What sample files are needed for this workflow? +--- +Based on the specification, the following sample files are needed for this workflow: + +1. `welcome.html` - This file should contain the HTML content for the welcome email sent to new subscribers. + +2. `coupon-newsletter.html` - This file should contain the HTML template for the weekly coupon newsletter email. + +3. `coupon-newsletter.txt` - This file should contain the plain text version of the weekly coupon newsletter email. + +4. `sample_coupons.json` - This file should contain a JSON array representing the coupon items to be included in the newsletter email. The JSON structure should be as follows: + +```json +{ + "coupons": [ + { + "details": "20% off on all electronics" + }, + { + "details": "Buy one, get one free on books" + }, + { + "details": "15% off on home appliances" + }, + { + "details": "Free shipping on orders over $50" + }, + { + "details": "25% off on outdoor gear" + }, + { + "details": "10% off on groceries" + } + ] +} +``` + +The workflow implementation will read the contents of these files at runtime and use them for the respective steps. \ No newline at end of file diff --git a/workflows/sesv2_weekly_mailer/content/41_welcome_html.md b/workflows/sesv2_weekly_mailer/content/41_welcome_html.md new file mode 100644 index 00000000000..9df06434de9 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/41_welcome_html.md @@ -0,0 +1,44 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: > + Create the welcome.html and welcome.txt sample files for the workflow + specification. +--- +welcome.html: + +```html + + + + Welcome to the Weekly Coupons Newsletter + + +

Welcome to the Weekly Coupons Newsletter!

+

Thank you for subscribing to our weekly newsletter. Every week, you'll receive a list of exciting coupons and deals from our partners.

+

Stay tuned for your first newsletter, coming soon!

+

Best regards,
The Weekly Coupons Team

+ + +``` + +welcome.txt: + +``` +Welcome to the Weekly Coupons Newsletter! + +Thank you for subscribing to our weekly newsletter. Every week, you'll receive a list of exciting coupons and deals from our partners. + +Stay tuned for your first newsletter, coming soon! + +Best regards, +The Weekly Coupons Team +``` + +These files contain a simple welcome message for new subscribers. The `welcome.html` file is an HTML version, while the `welcome.txt` file is a plain text version. The workflow implementation will read the contents of these files and use them as the content for the welcome email sent to new subscribers. \ No newline at end of file diff --git a/workflows/sesv2_weekly_mailer/content/42_coupon_newsletter_html.md b/workflows/sesv2_weekly_mailer/content/42_coupon_newsletter_html.md new file mode 100644 index 00000000000..afc667124a5 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/42_coupon_newsletter_html.md @@ -0,0 +1,61 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: | + Create the coupon-newsletter.html sample file for the workflow specification. +--- +```html + + + + Weekly Coupons Newsletter + + + +

Weekly Coupons Newsletter

+

Check out this week's hot deals and exclusive coupons!

+ +

+ Hurry, these offers won't last long! Visit our website or your nearest store to take advantage of these amazing deals. +

+

+ Happy shopping!
+ The Weekly Coupons Team +

+ + +``` + +This HTML file can be used as the `coupon-newsletter.html` sample file for the workflow specification. It includes a placeholder for inserting the coupon details using a template syntax (`{{#coupons}}{{details}}{{/coupons}}`). This placeholder will be replaced with the actual coupon data when the email template is rendered and sent using the SES v2 `SendEmail` API call. + +The HTML file also includes some basic styling to make the newsletter more visually appealing. \ No newline at end of file diff --git a/workflows/sesv2_weekly_mailer/content/43_coupon_newsletter_txt.md b/workflows/sesv2_weekly_mailer/content/43_coupon_newsletter_txt.md new file mode 100644 index 00000000000..4d22d94c342 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/43_coupon_newsletter_txt.md @@ -0,0 +1,32 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: | + Create the coupon-newsletter.txt sample file for the workflow specification. +--- +``` +Weekly Coupons Newsletter + +Check out this week's hot deals and exclusive coupons! + +{{#coupons}} +- {{details}} +{{/coupons}} + +Hurry, these offers won't last long! Visit our website or your nearest store to take advantage of these amazing deals. + +Happy shopping! +The Weekly Coupons Team +``` + +This plain text file can be used as the `coupon-newsletter.txt` sample file for the workflow specification. It follows a similar structure to the `coupon-newsletter.html` file but without any HTML tags or styling. + +The `{{#coupons}}{{details}}{{/coupons}}` placeholder will be replaced with the actual coupon details when the email template is rendered and sent using the SES v2 `SendEmail` API call. + +This text version of the newsletter can be used by email clients that do not support HTML or for recipients who prefer to receive plain text emails. \ No newline at end of file diff --git a/workflows/sesv2_weekly_mailer/content/44_sample_coupons.json b/workflows/sesv2_weekly_mailer/content/44_sample_coupons.json new file mode 100644 index 00000000000..e592a1f60a8 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/44_sample_coupons.json @@ -0,0 +1,33 @@ +--- +prompt: | + Generate a sample_coupons.json file for the SendEmail call. + + The JSON format for the Coupon should be an object with one key, `coupons`, + which is an array of items. Each coupon entry in the array should have one + key, `details`, with the details of the coupon. +--- + +```json +{ + "coupons": [ + { + "details": "20% off on all electronics" + }, + { + "details": "Buy one, get one free on books" + }, + { + "details": "15% off on home appliances" + }, + { + "details": "Free shipping on orders over $50" + }, + { + "details": "25% off on outdoor gear" + }, + { + "details": "10% off on groceries" + } + ] +} +``` diff --git a/workflows/sesv2_weekly_mailer/content/61a_NewsletterWorkflow.java.md b/workflows/sesv2_weekly_mailer/content/61a_NewsletterWorkflow.java.md new file mode 100644 index 00000000000..061e6b313c2 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/61a_NewsletterWorkflow.java.md @@ -0,0 +1,377 @@ +--- +skip: true +prompt: Generate the java implementation of this workflow. +--- + + + +```java +package com.example.sesv2; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sesv2.SesV2Client; +import software.amazon.awssdk.services.sesv2.model.*; + +/** + * This class implements the SES v2 Coupon Newsletter Workflow. + * It demonstrates how to use the Amazon Simple Email Service (SES) v2 to send a + * coupon newsletter to a list of contacts. + */ +public class NewsletterWorkflow { + private static final String CONTACT_LIST_NAME = "weekly-coupons-newsletter"; + private static final String TEMPLATE_NAME = "weekly-coupons"; + private static final String INTRO = """ + Welcome to the Amazon SES v2 Coupon Newsletter Workflow! + + This workflow will help you: + 1. Prepare a verified email identity and contact list for your newsletter. + 2. Gather subscriber email addresses and send them a welcome email. + 3. Send a weekly coupon newsletter to your subscribers using email templates. + 4. Monitor your sending activity and metrics in the AWS console. + + Let's get started! + """; + private final SesV2Client sesClient; + private String verifiedEmail = ""; + + public void test_setVerifiedEmail(String verifiedEmail) { + this.verifiedEmail = verifiedEmail; + } + + /** + * Constructor for the Workflow class. + * + * @param sesClient The SesV2Client instance to be used for interacting with the + * SES v2 service. + */ + public NewsletterWorkflow(SesV2Client sesClient) { + this.sesClient = sesClient; + } + + /** + * The main entry point of the application. + * + * @param args Command-line arguments. + */ + public static void main(String[] args) { + System.out.println(INTRO); + SesV2Client sesClient = SesV2Client.builder() + .region(Region.AWS_GLOBAL) + .build(); + + new NewsletterWorkflow(sesClient).run(); + } + + /** + * Orchestrates the execution of the workflow steps. + */ + public void run() { + try { + prepareApplication(); + gatherSubscriberEmails(); + sendCouponNewsletter(); + monitorAndReview(); + } catch (Exception e) { + } + cleanUp(); + } + + /** + * Prepares the application by creating an email identity and a contact list. + */ + public void prepareApplication() throws IOException { + try { + // 1. Create an email identity + System.out.println("Enter the verified email address: "); + Scanner scanner = new Scanner(System.in); + this.verifiedEmail = scanner.nextLine(); + scanner.close(); + CreateEmailIdentityRequest createEmailIdentityRequest = CreateEmailIdentityRequest.builder() + .emailIdentity(verifiedEmail) + .build(); + sesClient.createEmailIdentity(createEmailIdentityRequest); + System.out.println("Email identity created: " + verifiedEmail); + } catch (AlreadyExistsException e) { + System.out.println("Email identity already exists, skipping creation: " + verifiedEmail); + } catch (NotFoundException e) { + System.err.println("The provided email address is not verified: " + verifiedEmail); + throw e; + } catch (LimitExceededException e) { + System.err + .println("You have reached the limit for email identities. Please remove some identities and try again."); + throw e; + } catch (SesV2Exception e) { + System.err.println("Error creating email identity: " + e.getMessage()); + throw e; + } + + try { + // 2. Create a contact list + String contactListName = CONTACT_LIST_NAME; + CreateContactListRequest createContactListRequest = CreateContactListRequest.builder() + .contactListName(contactListName) + .build(); + sesClient.createContactList(createContactListRequest); + System.out.println("Contact list created: " + contactListName); + } catch (AlreadyExistsException e) { + System.out.println("Contact list already exists, skipping creation: weekly-coupons-newsletter"); + } catch (LimitExceededException e) { + System.err.println("Limit for contact lists has been exceeded."); + throw e; + } catch (SesV2Exception e) { + System.err.println("Error creating contact list: " + e.getMessage()); + throw e; + } + try { + // Create an email template named "weekly-coupons" + String newsletterHtml = Files.readString(Paths.get("resources/coupon_newsletter/coupon-newsletter.html")); + String newsletterText = Files.readString(Paths.get("resources/coupon_newsletter/coupon-newsletter.txt")); + + CreateEmailTemplateRequest templateRequest = CreateEmailTemplateRequest.builder() + .templateName(TEMPLATE_NAME) + .templateContent(EmailTemplateContent.builder() + .subject("Weekly Coupons Newsletter") + .html(newsletterHtml) + .text(newsletterText) + .build()) + .build(); + + sesClient.createEmailTemplate(templateRequest); + + System.out.println("Email template created: " + TEMPLATE_NAME); + } catch (AlreadyExistsException e) { + // If the template already exists, skip this step and proceed with the next + // operation + System.out.println("Email template already exists, skipping creation..."); + } catch (LimitExceededException e) { + // If the limit for email templates is exceeded, fail the workflow and inform + // the user + System.err.println("You have reached the limit for email templates. Please remove some templates and try again."); + throw e; + } catch (Exception e) { + System.err.println("Error occurred while creating email template: " + e.getMessage()); + throw e; + } + } + + /** + * Helper method to create subscriber subaddresses. + * + * @param baseEmail The base email address (e.g., "user@example.com") + * @return A list of three email addresses with subaddress extensions + */ + private List createSubscriberSubaddresses(String baseEmail) { + List subaddresses = new ArrayList<>(); + String[] parts = baseEmail.split("@"); + String username = parts[0]; + String domain = parts[1]; + + for (int i = 1; i <= 3; i++) { + String subaddress = username + "+ses-weekly-newsletter-" + i + "@" + domain; + subaddresses.add(subaddress); + } + + return subaddresses; + } + + /** + * Gathers subscriber email addresses and sends a welcome email to each new + * subscriber. + */ + public void gatherSubscriberEmails() throws IOException { + Scanner scanner = new Scanner(System.in); + System.out.print("Enter a base email address for subscribing to the newsletter: "); + String baseEmail = scanner.nextLine(); + scanner.close(); + + for (String emailAddress : createSubscriberSubaddresses(baseEmail)) { + try { + // Create a new contact with the provided email address in the + // "weekly-coupons-newsletter" contact list + CreateContactRequest contactRequest = CreateContactRequest.builder() + .contactListName(CONTACT_LIST_NAME) + .emailAddress(emailAddress) + .build(); + + sesClient.createContact(contactRequest); + + System.out.println("Contact created: " + emailAddress); + + // Send a welcome email to the new contact + String welcomeHtml = Files.readString(Paths.get("resources/coupon_newsletter/welcome.html")); + String welcomeText = Files.readString(Paths.get("resources/coupon_newsletter/welcome.txt")); + + SendEmailRequest welcomeEmailRequest = SendEmailRequest.builder() + .fromEmailAddress(this.verifiedEmail) + .destination(Destination.builder().toAddresses(emailAddress).build()) + .content(EmailContent.builder() + .simple( + Message.builder() + .subject(Content.builder().data("Welcome to the Weekly Coupons Newsletter").build()) + .body(Body.builder() + .text(Content.builder().data(welcomeText).build()) + .html(Content.builder().data(welcomeHtml).build()) + .build()) + .build()) + .build()) + .build(); + SendEmailResponse welcomeEmailResponse = sesClient.sendEmail(welcomeEmailRequest); + System.out.println("Welcome email sent: " + welcomeEmailResponse.messageId()); + } catch (AlreadyExistsException e) { + // If the contact already exists, skip this step for that contact and proceed + // with the next contact + System.out.println("Contact already exists, skipping creation..."); + } catch (Exception e) { + System.err.println("Error occurred while processing email address " + emailAddress + ": " + e.getMessage()); + throw e; + } + } + } + + /** + * Sends the coupon newsletter to the list of contacts. + */ + public void sendCouponNewsletter() { + try { + // Retrieve the list of contacts from the "weekly-coupons-newsletter" contact + // list + ListContactsRequest contactListRequest = ListContactsRequest.builder() + .contactListName(CONTACT_LIST_NAME) + .build(); + ListContactsResponse contactListResponse = sesClient.listContacts(contactListRequest); + List contactEmails = contactListResponse.contacts().stream() + .map(Contact::emailAddress) + .toList(); + + // Send an email using the "weekly-coupons" template to each contact in the list + String coupons = Files.readString(Paths.get("resources/coupon_newsletter/sample_coupons.json")); + for (String emailAddress : contactEmails) { + SendEmailRequest newsletterRequest = SendEmailRequest.builder() + .destination(Destination.builder().toAddresses(emailAddress).build()) + .content(EmailContent.builder() + .template(Template.builder() + .templateName(TEMPLATE_NAME) + .templateData(coupons) + .build()) + .build()) + .fromEmailAddress(this.verifiedEmail) + .listManagementOptions(ListManagementOptions.builder() + .contactListName(CONTACT_LIST_NAME) + .build()) + .build(); + SendEmailResponse newsletterResponse = sesClient.sendEmail(newsletterRequest); + System.out.println("Newsletter sent to " + emailAddress + ": " + newsletterResponse.messageId()); + } + } catch (NotFoundException e) { + // If the contact list does not exist, fail the workflow and inform the user + System.err.println("The contact list is missing. Please create the contact list and try again."); + } catch (AccountSuspendedException e) { + // If the account is suspended, fail the workflow and inform the user + System.err.println("Your account is suspended. Please resolve the issue and try again."); + } catch (MailFromDomainNotVerifiedException e) { + // If the sending domain is not verified, fail the workflow and inform the user + System.err.println("The sending domain is not verified. Please verify your domain and try again."); + throw e; + } catch (MessageRejectedException e) { + // If the message is rejected due to invalid content, fail the workflow and + // inform the user + System.err.println("The message content is invalid. Please check your template and try again."); + throw e; + } catch (SendingPausedException e) { + // If sending is paused, fail the workflow and inform the user + System.err.println("Sending is currently paused for your account. Please resolve the issue and try again."); + throw e; + } catch (Exception e) { + System.err.println("Error occurred while sending the newsletter: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Monitors and reviews the newsletter campaign. + */ + public void monitorAndReview() { + System.out.println("\nMonitor your sending activity using the SES Homepage in the AWS console:\n" + + "https://console.aws.amazon.com/ses/home#/account\n" + + "For more detailed monitoring, refer to the SES Developer Guide:\n" + + "https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity.html"); + Scanner scanner = new Scanner(System.in); + scanner.nextLine(); + scanner.close(); + } + + /** + * Cleans up the resources created during the workflow. + */ + public void cleanUp() { + try { + // Delete the contact list + DeleteContactListRequest deleteContactListRequest = DeleteContactListRequest.builder() + .contactListName(CONTACT_LIST_NAME) + .build(); + + sesClient.deleteContactList(deleteContactListRequest); + + System.out.println("Contact list deleted: " + CONTACT_LIST_NAME); + } catch (NotFoundException e) { + // If the contact list does not exist, log the error and proceed + System.out.println("Contact list not found. Skipping deletion..."); + } catch (Exception e) { + System.err.println("Error occurred while deleting the contact list: " + e.getMessage()); + e.printStackTrace(); + } + + try { + // Delete the template + DeleteEmailTemplateRequest deleteTemplateRequest = DeleteEmailTemplateRequest.builder() + .templateName(TEMPLATE_NAME) + .build(); + + sesClient.deleteEmailTemplate(deleteTemplateRequest); + + System.out.println("Email template deleted: " + TEMPLATE_NAME); + } catch (NotFoundException e) { + // If the email template does not exist, log the error and proceed + System.out.println("Email template not found. Skipping deletion..."); + } catch (Exception e) { + System.err.println("Error occurred while deleting the email template: " + e.getMessage()); + e.printStackTrace(); + } + + System.out.println("\nDo you want to delete the email identity? (y/n)"); + Scanner scanner = new Scanner(System.in); + String input = scanner.nextLine(); + scanner.close(); + + if (input.equalsIgnoreCase("y")) { + try { + // Delete the email identity + DeleteEmailIdentityRequest deleteIdentityRequest = DeleteEmailIdentityRequest.builder() + .emailIdentity(this.verifiedEmail) + .build(); + + sesClient.deleteEmailIdentity(deleteIdentityRequest); + + System.out.println("Email identity deleted: " + this.verifiedEmail); + } catch (NotFoundException e) { + // If the email identity does not exist, log the error and proceed + System.out.println("Email identity not found. Skipping deletion..."); + } catch (Exception e) { + System.err.println("Error occurred while deleting the email identity: " + e.getMessage()); + e.printStackTrace(); + } + } else { + System.out.println("Skipping email identity deletion."); + } + } +} +``` + + diff --git a/workflows/sesv2_weekly_mailer/content/62a_newsletter.rs.md b/workflows/sesv2_weekly_mailer/content/62a_newsletter.rs.md new file mode 100644 index 00000000000..a419bec6634 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/62a_newsletter.rs.md @@ -0,0 +1,399 @@ +--- +skip: true +prompt: Generate the rust implementation of this workflow. +--- + + + +```rust +use anyhow::{anyhow, Result}; +use aws_sdk_sesv2::{ + types::{ + Body, Contact, Content, Destination, EmailContent, EmailTemplateContent, + ListManagementOptions, Message, Template, + }, + Client, +}; +use std::io::{BufRead, Write}; +use tracing::info; + +const CONTACT_LIST_NAME: &str = "weekly-coupons-newsletter"; +const TEMPLATE_NAME: &str = "weekly-coupons"; +const TEMPLATE_HTML: &str = include_str!("../resources/newsletter/coupon-newsletter.html"); +const TEMPLATE_TEXT: &str = include_str!("../resources/newsletter/coupon-newsletter.txt"); +const WELCOME_HTML: &str = include_str!("../resources/newsletter/welcome.html"); +const WELCOME_TXT: &str = include_str!("../resources/newsletter/welcome.txt"); +const COUPONS: &str = include_str!("../resources/newsletter/sample_coupons.json"); + +/// The SESWorkflow struct encapsulates the entire SES v2 Coupon Newsletter Workflow. +pub struct SESWorkflow<'a> { + client: Client, + stdin: &'a mut dyn BufRead, + stdout: &'a mut dyn Write, + verified_email: String, +} + +impl<'a> SESWorkflow<'a> { + /// Creates a new instance of the SESWorkflow struct. + /// + /// # Arguments + /// + /// * `client` - The AWS SDK for Rust SES v2 client. + /// * `stdin` - A mutable reference to the standard input stream. + /// * `stdout` - A mutable reference to the standard output stream. + pub fn new(client: Client, stdin: &'a mut dyn BufRead, stdout: &'a mut dyn Write) -> Self { + Self { + client, + stdin, + stdout, + verified_email: "".into(), + } + } + + /// Prepares the application by creating a verified email identity and a contact list. + pub async fn prepare_application(&mut self) -> Result<()> { + // Prompt the user for a verified email address + writeln!(self.stdout, "Enter the verified email address to use: ")?; + self.stdout.flush().unwrap(); + let mut verified_email = String::new(); + self.stdin.read_line(&mut verified_email).unwrap(); + self.verified_email = verified_email.trim().to_string(); + + match self + .client + .create_email_identity() + .email_identity(self.verified_email.clone()) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Email identity created successfully.")?, + Err(e) => { + if e.as_service_error().unwrap().is_already_exists_exception() { + writeln!( + self.stdout, + "Email identity already exists, skipping creation." + )?; + } else { + return Err(anyhow!("Error creating email identity: {}", e)); + } + } + } + + // Create the contact list + match self + .client + .create_contact_list() + .contact_list_name(CONTACT_LIST_NAME) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Contact list created successfully.")?, + Err(e) => { + if e.as_service_error().unwrap().is_already_exists_exception() { + writeln!( + self.stdout, + "Contact list already exists, skipping creation." + )?; + } else { + return Err(anyhow!("Error creating contact list: {}", e)); + } + } + } + + // Create the email template + let template_content = EmailTemplateContent::builder() + .subject("Weekly Coupons Newsletter") + .html(TEMPLATE_HTML) + .text(TEMPLATE_TEXT) + .build(); + + match self + .client + .create_email_template() + .template_name(TEMPLATE_NAME) + .template_content(template_content) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Email template created successfully.")?, + Err(e) => { + if e.as_service_error().unwrap().is_already_exists_exception() { + writeln!( + self.stdout, + "Email template already exists, skipping creation." + )?; + } else { + return Err(anyhow!("Error creating email template: {}", e)); + } + } + } + + Ok(()) + } + + /// Gathers subscriber email addresses and sends welcome emails. + pub async fn gather_subscriber_emails(&mut self) -> Result<()> { + // Prompt the user for a base email address + writeln!( + self.stdout, + "Enter a base email address for subscribing (e.g., user@example.com): " + )?; + self.stdout.flush().unwrap(); + let mut base_email = String::new(); + self.stdin.read_line(&mut base_email).unwrap(); + let base_email = base_email.trim().to_string(); + + // Create 3 variants of the email address as {user email}+ses-weekly-newsletter-{i}@{user domain} + let (user_email, user_domain) = base_email.split_once('@').unwrap(); + let mut emails = Vec::with_capacity(3); + for i in 1..=3 { + let email = format!("{}+ses-weekly-newsletter-{}@{}", user_email, i, user_domain); + emails.push(email); + } + + // Create a contact and send a welcome email for each email address + for email in emails { + // Create the contact + match self + .client + .create_contact() + .contact_list_name(CONTACT_LIST_NAME) + .email_address(email.clone()) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Contact created for {}", email)?, + Err(e) => { + if e.as_service_error().unwrap().is_already_exists_exception() { + writeln!( + self.stdout, + "Contact already exists for {}, skipping creation.", + email + )?; + } else { + return Err(anyhow!("Error creating contact for {}: {}", email, e)); + } + } + } + + // Send the welcome email + let email_content = EmailContent::builder() + .simple( + Message::builder() + .subject( + Content::builder() + .data("Welcome to the Weekly Coupons Newsletter") + .build()?, + ) + .body( + Body::builder() + .html(Content::builder().data(WELCOME_HTML).build()?) + .text(Content::builder().data(WELCOME_TXT).build()?) + .build(), + ) + .build(), + ) + .build(); + + match self + .client + .send_email() + .from_email_address(self.verified_email.clone()) + .destination(Destination::builder().to_addresses(email.clone()).build()) + .content(email_content) + .send() + .await + { + Ok(output) => { + if let Some(message_id) = output.message_id { + writeln!( + self.stdout, + "Welcome email sent to {} with message ID {}", + email, message_id + )?; + } else { + writeln!(self.stdout, "Welcome email sent to {}", email)?; + } + } + Err(e) => return Err(anyhow!("Error sending welcome email to {}: {}", email, e)), + } + } + + Ok(()) + } + + /// Sends the coupon newsletter to the subscribers. + pub async fn send_coupon_newsletter(&mut self) -> Result<()> { + // Retrieve the list of contacts + let contacts: Vec = match self + .client + .list_contacts() + .contact_list_name(CONTACT_LIST_NAME) + .send() + .await + { + Ok(list_contacts_output) => { + list_contacts_output.contacts.unwrap().into_iter().collect() + } + Err(e) => { + return Err(anyhow!( + "Error retrieving contact list {}: {}", + CONTACT_LIST_NAME, + e + )) + } + }; + + // Send the newsletter to each contact + for email in contacts { + let email = email.email_address.unwrap(); + let email_content = EmailContent::builder() + .template( + Template::builder() + .template_name(TEMPLATE_NAME) + .template_data(COUPONS) + .build(), + ) + .build(); + + match self + .client + .send_email() + .from_email_address(self.verified_email.clone()) + .destination(Destination::builder().to_addresses(email.clone()).build()) + .content(email_content) + .list_management_options( + ListManagementOptions::builder() + .contact_list_name(CONTACT_LIST_NAME) + .build()?, + ) + .send() + .await + { + Ok(output) => { + if let Some(message_id) = output.message_id { + writeln!( + self.stdout, + "Newsletter sent to {} with message ID {}", + email, message_id + )?; + } else { + writeln!(self.stdout, "Newsletter sent to {}", email)?; + } + } + Err(e) => return Err(anyhow!("Error sending newsletter to {}: {}", email, e)), + } + } + + Ok(()) + } + + /// Monitors the sending activity and provides insights. + pub async fn monitor(&mut self) -> Result<()> { + // Check if the user wants to review the monitoring dashboard + writeln!( + self.stdout, + "Do you want to review the monitoring dashboard? (y/n): " + )?; + self.stdout.flush().unwrap(); + let mut response = String::new(); + self.stdin.read_line(&mut response).unwrap(); + + if response.trim().eq_ignore_ascii_case("y") { + // Open the SES monitoring dashboard in the default browser + open::that("https://console.aws.amazon.com/ses/home#/account")?; + + writeln!( + self.stdout, + "The SES monitoring dashboard has been opened in your default browser." + )?; + writeln!( + self.stdout, + "Review the sending activity, open and click rates, bounces, complaints, and more." + )?; + } else { + writeln!(self.stdout, "Skipping the monitoring dashboard review.")?; + } + + writeln!(self.stdout, "Press any key to continue.")?; + self.stdout.flush().unwrap(); + let mut response = String::new(); + self.stdin.read_line(&mut response).unwrap(); + + Ok(()) + } + + /// Cleans up the resources created during the workflow. + pub async fn cleanup(&mut self) -> Result<()> { + info!("Cleaning up resources..."); + + match self + .client + .delete_contact_list() + .contact_list_name(CONTACT_LIST_NAME) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Contact list deleted successfully.")?, + Err(e) => return Err(anyhow!("Error deleting contact list: {e}")), + } + + match self + .client + .delete_email_template() + .template_name(TEMPLATE_NAME) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Email template deleted successfully.")?, + Err(e) => { + return Err(anyhow!("Error deleting email template: {e}")); + } + } + + // Delete the email identity + writeln!( + self.stdout, + "Do you want to delete the verified email identity? (y/n): " + )?; + self.stdout.flush().unwrap(); + let mut response = String::new(); + self.stdin.read_line(&mut response).unwrap(); + + if response.trim().eq_ignore_ascii_case("y") { + match self + .client + .delete_email_identity() + .email_identity(self.verified_email.clone()) + .send() + .await + { + Ok(_) => writeln!(self.stdout, "Email identity deleted successfully.")?, + Err(e) => { + return Err(anyhow!("Error deleting email identity: {}", e)); + } + } + } else { + writeln!(self.stdout, "Skipping deletion of email identity.")?; + } + + info!("Cleanup completed."); + + Ok(()) + } + + pub async fn run(&mut self) -> Result<()> { + self.prepare_application().await?; + self.gather_subscriber_emails().await?; + self.send_coupon_newsletter().await?; + self.monitor().await?; + Ok(()) + } + + pub fn set_verified_email(&mut self, verified_email: String) { + self.verified_email = verified_email; + } +} +``` + + diff --git a/workflows/sesv2_weekly_mailer/content/63a_newsletter.py.md b/workflows/sesv2_weekly_mailer/content/63a_newsletter.py.md new file mode 100644 index 00000000000..8e9ae3a9ba3 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/63a_newsletter.py.md @@ -0,0 +1,301 @@ +--- +skip: true +prompt: Generate the rust implementation of this workflow. +--- + + + +```python +import boto3 +from botocore.exceptions import ClientError +from time import sleep + +# Constants +CONTACT_LIST_NAME = "weekly-coupons-newsletter" +TEMPLATE_NAME = "weekly-coupons" + +INTRO = """ +Welcome to the Amazon SES v2 Coupon Newsletter Workflow! + +This workflow will help you: +1. Prepare a verified email identity and contact list for your newsletter. +2. Gather subscriber email addresses and send them a welcome email. +3. Send a weekly coupon newsletter to your subscribers using email templates. +4. Monitor your sending activity and metrics in the AWS console. + +Let's get started! +""" + + +# Helper functions +def load_file_content(file_path): + """ + Loads the content of a file. + + Args: + file_path (str): The path to the file. + + Returns: + str: The content of the file. + """ + with open(file_path, "r") as file: + content = file.read() + return content + + +def print_error(error): + """ + Prints the error message to the console. + + Args: + error (Exception): The exception object. + """ + print(f"Error: {error}") + + +def get_subaddress_variants(base_email, num_variants): + """ + Generates subaddress variants of a base email address. + + Args: + base_email (str): The base email address. + num_variants (int): The number of variants to generate. + + Returns: + list: A list of subaddress variants. + """ + user_part, domain_part = base_email.split("@") + variants = [ + f"{user_part}+ses-weekly-newsletter-{i}@{domain_part}" + for i in range(1, num_variants + 1) + ] + return variants + + +class SESv2Workflow: + """ + A class to manage the SES v2 Coupon Newsletter Workflow. + """ + + def __init__(self, ses_client, sleep=True): + self.ses_client = ses_client + self.sleep = sleep + + def prepare_application(self): + """ + Prepares the application by creating an email identity and a contact list. + """ + # Get the verified email address from the user + self.verified_email = input("Enter the verified email address: ") + + # Create the email identity + try: + self.ses_client.create_email_identity(EmailIdentity=self.verified_email) + print(f"Email identity '{self.verified_email}' created successfully.") + except ClientError as e: + # If the email identity already exists, skip and proceed + if e.response["Error"]["Code"] == "AlreadyExistsException": + print(f"Email identity '{self.verified_email}' already exists.") + else: + raise e + + # Create the contact list + try: + self.ses_client.create_contact_list(ContactListName=CONTACT_LIST_NAME) + print(f"Contact list '{CONTACT_LIST_NAME}' created successfully.") + except ClientError as e: + # If the contact list already exists, skip and proceed + if e.response["Error"]["Code"] == "AlreadyExistsException": + print(f"Contact list '{CONTACT_LIST_NAME}' already exists.") + else: + raise e + + # Create the email template + try: + template_content = { + "Subject": "Weekly Coupons Newsletter", + "Html": load_file_content("coupon-newsletter.html"), + "Text": load_file_content("coupon-newsletter.txt"), + } + self.ses_client.create_email_template( + TemplateName=TEMPLATE_NAME, TemplateContent=template_content + ) + print(f"Email template '{TEMPLATE_NAME}' created successfully.") + except ClientError as e: + # If the template already exists, skip and proceed + if e.response["Error"]["Code"] == "AlreadyExistsException": + print(f"Email template '{TEMPLATE_NAME}' already exists.") + else: + raise e + + def gather_subscriber_email_addresses(self): + """ + Gathers subscriber email addresses and sends a welcome email to each subscriber. + """ + # Get the base email address from the user + base_email = input( + "Enter a base email address for subscribing to the newsletter: " + ) + + # Generate subaddress variants + email_variants = get_subaddress_variants(base_email, 3) + + # Load the welcome email content + welcome_text = load_file_content("welcome.txt") + welcome_html = load_file_content("welcome.html") + + # Create contacts and send welcome emails + for email in email_variants: + try: + # Create a new contact + self.ses_client.create_contact( + ContactListName=CONTACT_LIST_NAME, EmailAddress=email + ) + print(f"Contact with email '{email}' created successfully.") + + # Send the welcome email + self.ses_client.send_email( + FromEmailAddress=self.verified_email, + Destination={"ToAddresses": [email]}, + Content={ + "Simple": { + "Subject": { + "Data": "Welcome to the Weekly Coupons Newsletter" + }, + "Body": { + "Text": {"Data": welcome_text}, + "Html": {"Data": welcome_html}, + }, + } + }, + ) + print(f"Welcome email sent to '{email}'.") + if self.sleep: + # 1 email per second in sandbox mode, remove in production. + sleep(1.1) + except ClientError as e: + # If the contact already exists, skip and proceed + if e.response["Error"]["Code"] == "AlreadyExistsException": + print(f"Contact with email '{email}' already exists. Skipping...") + else: + raise e + + def send_coupon_newsletter(self): + """ + Sends the coupon newsletter to the subscribers. + """ + # Get the list of contacts + try: + contacts_response = self.ses_client.list_contacts( + ContactListName=CONTACT_LIST_NAME + ) + except ClientError as e: + if e.response["Error"]["Code"] == "NotFoundException": + print(f"Contact list '{CONTACT_LIST_NAME}' does not exist.") + return + else: + raise e + + # Send the coupon newsletter to each contact + coupon_items = load_file_content("sample_coupons.json") + + for contact in contacts_response["Contacts"]: + email_address = contact["EmailAddress"] + try: + send = self.ses_client.send_email( + FromEmailAddress=self.verified_email, + Destination={"ToAddresses": [email_address]}, + Content={ + "Template": { + "TemplateName": TEMPLATE_NAME, + "TemplateData": coupon_items, + } + }, + ListManagementOptions={"ContactListName": CONTACT_LIST_NAME}, + ) + print(f"Newsletter sent to '{email_address}'.") + print("Debug: ", send) + if self.sleep: + # 1 email per second in sandbox mode, remove in production. + sleep(1.1) + except ClientError as e: + print_error(e) + + def monitor_and_review(self): + """ + Provides instructions for monitoring sending activity in the AWS console. + """ + print( + "To monitor your sending activity, please visit the SES Homepage in the AWS console:" + ) + print("https://console.aws.amazon.com/ses/home#/account") + print( + "From there, you can view various dashboards and metrics related to your newsletter campaign." + ) + input("Press enter to continue.") + + def clean_up(self): + """ + Cleans up the resources created during the workflow. + """ + # Delete the contact list + try: + self.ses_client.delete_contact_list(ContactListName=CONTACT_LIST_NAME) + print(f"Contact list '{CONTACT_LIST_NAME}' deleted successfully.") + except ClientError as e: + # If the contact list doesn't exist, skip and proceed + if e.response["Error"]["Code"] == "NotFoundException": + print(f"Contact list '{CONTACT_LIST_NAME}' does not exist.") + else: + print(e) + + # Delete the email template + try: + self.ses_client.delete_email_template(TemplateName=TEMPLATE_NAME) + print(f"Email template '{TEMPLATE_NAME}' deleted successfully.") + except ClientError as e: + # If the email template doesn't exist, skip and proceed + if e.response["Error"]["Code"] == "NotFoundException": + print(f"Email template '{TEMPLATE_NAME}' does not exist.") + else: + print(e) + + # Ask the user if they want to delete the email identity + delete_identity = input("Do you want to delete the email identity? (y/n) ") + if delete_identity.lower() == "y": + try: + self.ses_client.delete_email_identity(EmailIdentity=self.verified_email) + print(f"Email identity '{self.verified_email}' deleted successfully.") + except ClientError as e: + # If the email identity doesn't exist, skip and proceed + if e.response["Error"]["Code"] == "NotFoundException": + print(f"Email identity '{self.verified_email}' does not exist.") + else: + print(e) + else: + print("Skipping email identity deletion.") + + +# Main function +def main(): + """ + The main function that orchestrates the execution of the workflow. + """ + print(INTRO) + ses_client = boto3.client("sesv2") + workflow = SESv2Workflow(ses_client) + try: + workflow.prepare_application() + workflow.gather_subscriber_email_addresses() + workflow.send_coupon_newsletter() + workflow.monitor_and_review() + except ClientError as e: + print_error(e) + workflow.clean_up() + + +if __name__ == "__main__": + main() +``` + + diff --git a/workflows/sesv2_weekly_mailer/content/70_snippets.md b/workflows/sesv2_weekly_mailer/content/70_snippets.md new file mode 100644 index 00000000000..b669ee095bb --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/70_snippets.md @@ -0,0 +1,44 @@ +--- +skip: true +prompt: | + Create an example block of YAML to describe metadata snippets. This block + describes one example for the EC2 StartInstances action, in Java, Python, and Rust. +--- + +```yaml +c2_StartInstances: + title: Start an &EC2; instance using an &AWS; SDK + title_abbrev: Start an instance + synopsis: start an &EC2; instance. + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ec2 + sdkguide: + excerpts: + - description: + snippet_tags: + - ec2.java2.scenario.start_instance.main + Python: + versions: + - sdk_version: 3 + github: python/example_code/ec2 + sdkguide: + excerpts: + - description: + snippet_tags: + - python.example_code.ec2.InstanceWrapper.decl + - python.example_code.ec2.StartInstances + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ec2 + excerpts: + - description: + snippet_tags: + - ec2.rust.start-instance + services: + ec2: { StartInstances } +``` diff --git a/workflows/sesv2_weekly_mailer/content/71_metadata.md b/workflows/sesv2_weekly_mailer/content/71_metadata.md new file mode 100644 index 00000000000..25f2ac6690b --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/71_metadata.md @@ -0,0 +1,302 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +skip: true +prompt: > + Generate yaml for each sesv2 action in the workflow, in each language. + + IDs: sesv2_{Action} + + Java: github: javav2/example_code/ses tags: sesv2.java2.newsletter.{action} + + Python: github: python/example_code/sesv2 tags: python.example_code.sesv2.{action} + + Rust: github: rustv1/examples/ses tags: sesv2.rust.{example} +--- + +```yaml +sesv2_CreateContact: + title: Add a contact to a contact list using Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.CreateContact + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.CreateContact + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.create-contact + services: + sesv2: { CreateContact } + +sesv2_CreateContactList: + title: Create a contact list for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.CreateContactList + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.CreateContactList + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.create-contact-list + services: + sesv2: { CreateContactList } + +sesv2_CreateEmailIdentity: + title: Create an email identity for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.CreateEmailIdentity + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.CreateEmailIdentity + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.create-email-identity + services: + sesv2: { CreateEmailIdentity } + +sesv2_CreateEmailTemplate: + title: Create an email template for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.CreateEmailTemplate + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.CreateEmailTemplate + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.create-email-template + services: + sesv2: { CreateEmailTemplate } + +sesv2_DeleteContactList: + title: Delete a contact list for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.DeleteContactList + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.DeleteContactList + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.delete-contact-list + services: + sesv2: { DeleteContactList } + +sesv2_DeleteEmailIdentity: + title: Delete an email identity for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.DeleteEmailIdentity + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.DeleteEmailIdentity + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.delete-email-identity + services: + sesv2: { DeleteEmailIdentity } + +sesv2_DeleteEmailTemplate: + title: Delete an email template for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.DeleteEmailTemplate + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.DeleteEmailTemplate + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.delete-email-template + services: + sesv2: { DeleteEmailTemplate } + +sesv2_ListContacts: + title: List contacts in a contact list for Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.ListContacts + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.ListContacts + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.list-contacts + services: + sesv2: { ListContacts } + +sesv2_SendEmail: + title: Send an email using Amazon SES v2 + category: + languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/ses + excerpts: + - description: + snippet_tags: + - sesv2.java2.newsletter.SendEmail + Python: + versions: + - sdk_version: 3 + github: python/example_code/sesv2 + excerpts: + - description: + snippet_tags: + - python.example_code.sesv2.SendEmail + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/ses + excerpts: + - description: + snippet_tags: + - sesv2.rust.send-email + services: + sesv2: { SendEmail } +``` diff --git a/workflows/sesv2_weekly_mailer/content/934a_README_CLEANUP_LINKS.md b/workflows/sesv2_weekly_mailer/content/934a_README_CLEANUP_LINKS.md new file mode 100644 index 00000000000..9423a758ff5 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/934a_README_CLEANUP_LINKS.md @@ -0,0 +1,33 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: false +prompt: > + Create the remaining SES v2 Actions Used links for the Cleanup step. Use the + same format as the links + + + Current links: + - [`CreateContact`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContact.html) + - [`CreateContactList`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContactList.html) + - [`CreateEmailIdentity`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailIdentity.html) + - [`CreateEmailTemplate`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailTemplate.html) + - [`ListContacts`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListContacts.html) + - [`SendEmail`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html) (with both Simple and Template formats) + + New links: + - Operation: **DeleteContactList** + - Operation: **DeleteEmailTemplate** + - Operation: **DeleteEmailIdentity** +--- + +Here are the remaining SES v2 Actions Used links for the Cleanup step: + +- [`DeleteContactList`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteContactList.html) +- [`DeleteEmailTemplate`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteEmailTemplate.html) +- [`DeleteEmailIdentity`](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_DeleteEmailIdentity.html) diff --git a/workflows/sesv2_weekly_mailer/content/tmp/.aillyrc b/workflows/sesv2_weekly_mailer/content/tmp/.aillyrc new file mode 100644 index 00000000000..4ec4efae53d --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/tmp/.aillyrc @@ -0,0 +1,3 @@ +--- +isolated: true +--- \ No newline at end of file diff --git a/workflows/sesv2_weekly_mailer/content/tmp/api_ref.md b/workflows/sesv2_weekly_mailer/content/tmp/api_ref.md new file mode 100644 index 00000000000..11fb046f5e5 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/tmp/api_ref.md @@ -0,0 +1,43 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: > + Create a script that uses curl commands to download the API documents for each + API used. + + API Ref urls look like + https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContact.html + + Initialize the destination files with a markdown yaml header with a single + property `skip: true`. + + After the curl command, include a pipe that uses `pup` to select only the + main-col-body: `pup "#main-col-body"`, pipe that through the program + `html2text -nobs -utf8`, and finally append to the file `10_{apiname}.md`. +--- + +```bash +echo "---\nskip: true\n---" > 10_CreateContact.md +curl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContact.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_CreateContact.md + +echo "---\nskip: true\n---" > 10_CreateContactList.md +curl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateContactList.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_CreateContactList.md + +echo "---\nskip: true\n---" > 10_CreateEmailIdentity.md +curl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailIdentity.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_CreateEmailIdentity.md + +echo "---\nskip: true\n---" > 10_CreateEmailTemplate.md +curl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_CreateEmailTemplate.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_CreateEmailTemplate.md + +echo "---\nskip: true\n---" > 10_ListContacts.md +curl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListContacts.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_ListContacts.md + +echo "---\nskip: true\n---" > 10_SendEmail.md +curl https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_SendEmail.md +``` diff --git a/workflows/sesv2_weekly_mailer/content/tmp/clean_api_ref.md b/workflows/sesv2_weekly_mailer/content/tmp/clean_api_ref.md new file mode 100644 index 00000000000..ba70d7a5695 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/tmp/clean_api_ref.md @@ -0,0 +1,27 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: > + Create a shell script that finds all files named 10_*.md in the current + folder, and removes all content from the "See Also" line to the end of the file. The operation should work in place. +--- + +`sed -i _ '/See Also/,$d' 10_*.md` + +This command does the following: + +1. `sed` is the stream editor utility used to perform text transformations. +2. `'/^See Also/,$d'` is the sed command to delete lines. Specifically: + - `^See Also` matches the line that starts with "See Also" + - `,$` means "from the matched line to the end of the file" + - `d` is the delete command +3. `10_*.md` is the pattern that matches all filenames starting with "10\_" and ending with ".md" in the current directory. +4. `>` redirects the output to a new file called `cleaned_files.md`. + +So, this command will read all files matching the "10\_\*.md" pattern, remove everything from the "See Also" line to the end of each file, and write the cleaned content to a new file "cleaned_files.md". diff --git a/workflows/sesv2_weekly_mailer/content/tmp/links.md b/workflows/sesv2_weekly_mailer/content/tmp/links.md new file mode 100644 index 00000000000..d02cfe60766 --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/tmp/links.md @@ -0,0 +1,347 @@ +--- +combined: true +debug: + engine: bedrock + finish: max_tokens + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: > + Generate titles for each of these actions in the SESv2 API. + + + Output the data in a JSON array, where each entry is an object with a key + `action` containing the action, and a key `title` with the generated title. + + + Title should be human readable and in the imperative case. Titles inform + builders what they will use the action to achieve. + + + The following actions are supported: + + + - BatchGetMetricData + + - CancelExportJob + + - CreateConfigurationSet + + - CreateConfigurationSetEventDestination + + - CreateContact + + - CreateContactList + + - CreateCustomVerificationEmailTemplate + + - CreateDedicatedIpPool + + - CreateDeliverabilityTestReport + + - CreateEmailIdentity + + - CreateEmailIdentityPolicy + + - CreateEmailTemplate + + - CreateExportJob + + - CreateImportJob + + - DeleteConfigurationSet + + - DeleteConfigurationSetEventDestination + + - DeleteContact + + - DeleteContactList + + - DeleteCustomVerificationEmailTemplate + + - DeleteDedicatedIpPool + + - DeleteEmailIdentity + + - DeleteEmailIdentityPolicy + + - DeleteEmailTemplate + + - DeleteSuppressedDestination + + - GetAccount + + - GetBlacklistReports + + - GetConfigurationSet + + - GetConfigurationSetEventDestinations + + - GetContact + + - GetContactList + + - GetCustomVerificationEmailTemplate + + - GetDedicatedIp + + - GetDedicatedIpPool + + - GetDedicatedIps + + - GetDeliverabilityDashboardOptions + + - GetDeliverabilityTestReport + + - GetDomainDeliverabilityCampaign + + - GetDomainStatisticsReport + + - GetEmailIdentity + + - GetEmailIdentityPolicies + + - GetEmailTemplate + + - GetExportJob + + - GetImportJob + + - GetMessageInsights + + - GetSuppressedDestination + + - ListConfigurationSets + + - ListContactLists + + - ListContacts + + - ListCustomVerificationEmailTemplates + + - ListDedicatedIpPools + + - ListDeliverabilityTestReports + + - ListDomainDeliverabilityCampaigns + + - ListEmailIdentities + + - ListEmailTemplates + + - ListExportJobs + + - ListImportJobs + + - ListRecommendations + + - ListSuppressedDestinations + + - ListTagsForResource + + - PutAccountDedicatedIpWarmupAttributes + + - PutAccountDetails + + - PutAccountSendingAttributes + + - PutAccountSuppressionAttributes + + - PutAccountVdmAttributes + + - PutConfigurationSetDeliveryOptions + + - PutConfigurationSetReputationOptions + + - PutConfigurationSetSendingOptions + + - PutConfigurationSetSuppressionOptions + + - PutConfigurationSetTrackingOptions + + - PutConfigurationSetVdmOptions + + - PutDedicatedIpInPool + + - PutDedicatedIpPoolScalingAttributes + + - PutDedicatedIpWarmupAttributes + + - PutDeliverabilityDashboardOption + + - PutEmailIdentityConfigurationSetAttributes + + - PutEmailIdentityDkimAttributes + + - PutEmailIdentityDkimSigningAttributes + + - PutEmailIdentityFeedbackAttributes + + - PutEmailIdentityMailFromAttributes + + - PutSuppressedDestination + + - SendBulkEmail + + - SendCustomVerificationEmail + + - SendEmail + + - TagResource + + - TestRenderEmailTemplate + + - UntagResource + + - UpdateConfigurationSetEventDestination + + - UpdateContact + + - UpdateContactList + + - UpdateCustomVerificationEmailTemplate + + - UpdateEmailIdentityPolicy + + - UpdateEmailTemplate +--- +[ + { + "action": "BatchGetMetricData", + "title": "Retrieve Batched Metric Data" + }, + { + "action": "CancelExportJob", + "title": "Cancel an Export Job" + }, + { + "action": "CreateConfigurationSet", + "title": "Create a Configuration Set" + }, + { + "action": "CreateConfigurationSetEventDestination", + "title": "Create a Configuration Set Event Destination" + }, + { + "action": "CreateContact", + "title": "Create a Contact" + }, + { + "action": "CreateContactList", + "title": "Create a Contact List" + }, + { + "action": "CreateCustomVerificationEmailTemplate", + "title": "Create a Custom Verification Email Template" + }, + { + "action": "CreateDedicatedIpPool", + "title": "Create a Dedicated IP Pool" + }, + { + "action": "CreateDeliverabilityTestReport", + "title": "Create a Deliverability Test Report" + }, + { + "action": "CreateEmailIdentity", + "title": "Create an Email Identity" + }, + { + "action": "CreateEmailIdentityPolicy", + "title": "Create an Email Identity Policy" + }, + { + "action": "CreateEmailTemplate", + "title": "Create an Email Template" + }, + { + "action": "CreateExportJob", + "title": "Create an Export Job" + }, + { + "action": "CreateImportJob", + "title": "Create an Import Job" + }, + { + "action": "DeleteConfigurationSet", + "title": "Delete a Configuration Set" + }, + { + "action": "DeleteConfigurationSetEventDestination", + "title": "Delete a Configuration Set Event Destination" + }, + { + "action": "DeleteContact", + "title": "Delete a Contact" + }, + { + "action": "DeleteContactList", + "title": "Delete a Contact List" + }, + { + "action": "DeleteCustomVerificationEmailTemplate", + "title": "Delete a Custom Verification Email Template" + }, + { + "action": "DeleteDedicatedIpPool", + "title": "Delete a Dedicated IP Pool" + }, + { + "action": "DeleteEmailIdentity", + "title": "Delete an Email Identity" + }, + { + "action": "DeleteEmailIdentityPolicy", + "title": "Delete an Email Identity Policy" + }, + { + "action": "DeleteEmailTemplate", + "title": "Delete an Email Template" + }, + { + "action": "DeleteSuppressedDestination", + "title": "Delete a Suppressed Destination" + }, + { + "action": "GetAccount", + "title": "Get Account Details" + }, + { + "action": "GetBlacklistReports", + "title": "Get Blacklist Reports" + }, + { + "action": "GetConfigurationSet", + "title": "Get a Configuration Set" + }, + { + "action": "GetConfigurationSetEventDestinations", + "title": "Get Configuration Set Event Destinations" + }, + { + "action": "GetContact", + "title": "Get a Contact" + }, + { + "action": "GetContactList", + "title": "Get a Contact List" + }, + { + "action": "GetCustomVerificationEmailTemplate", + "title": "Get a Custom Verification Email Template" + }, + { + "action": "GetDedicatedIp", + "title": "Get a Dedicated IP" + }, + { + "action": "GetDedicatedIpPool", + "title": "Get a Dedicated IP Pool" + }, + { + "action": "GetDedicatedIps", + "title": "Get Dedicated IPs" + }, + { + "action \ No newline at end of file diff --git a/workflows/sesv2_weekly_mailer/content/tmp/pyref.md b/workflows/sesv2_weekly_mailer/content/tmp/pyref.md new file mode 100644 index 00000000000..12f010ffb2b --- /dev/null +++ b/workflows/sesv2_weekly_mailer/content/tmp/pyref.md @@ -0,0 +1,57 @@ +--- +combined: true +debug: + engine: bedrock + finish: end_turn + id: null + model: anthropic.claude-3-sonnet-20240229-v1:0 + usage: null +isolated: true +prompt: > + Create a script that uses curl commands to download the python API documents + for each + + API used. The python API is the lower snake case of the API Call, for + instance, CreateContact becomes create_contact. + + + python API Ref urls look like + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/create_contact.html + + + Initialize the destination files with a markdown yaml header with a single + + property `skip: true`. + + + After the curl command, include a pipe that uses `pup` to select only the id + with the API name: `pup "#{apiname}"`, pipe that through the program + `html2text -nobs -utf8`, and finally append to the file `10_{apiname}.md`. + + One complete command would be: + + ``` + echo "---\nskip: true\n---" > 10_create_contact.md curl + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/create_contact.html | pup "#create_contact" | html2text -nobs -utf8 >> 10_create_contact.md + ``` +--- + +```bash +echo "---\nskip: true\n---" > 10_create_contact.md +curl https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/create_contact.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_create_contact.md + +echo "---\nskip: true\n---" > 10_create_contact_list.md +curl https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/create_contact_list.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_create_contact_list.md + +echo "---\nskip: true\n---" > 10_create_email_identity.md +curl https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/create_email_identity.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_create_email_identity.md + +echo "---\nskip: true\n---" > 10_create_email_template.md +curl https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/create_email_template.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_create_email_template.md + +echo "---\nskip: true\n---" > 10_list_contacts.md +curl https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/list_contacts.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_list_contacts.md + +echo "---\nskip: true\n---" > 10_send_email.md +curl https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/send_email.html | pup "#main-col-body" | html2text -nobs -utf8 >> 10_send_email.md +```