AWS

AWS SWF

What are we talking about this time?

Last time we talked about Email Service (SES). This time we will talk about AWS SWF.

Initial setup

If you did not read the very first part of this series of posts, I urge you to go and read that one now as it shows you how to get started with AWS, and create an IAM user : https://sachabarbs.wordpress.com/2018/08/30/aws-initial-setup/

Where is the code

The code for this post can be found here in GitHub : https://github.com/sachabarber/AWS/tree/master/Compute/SWF

What are we talking about this time?

This time we will be talking about SWF (which stands for simple workflow service), which Amazon have this to say about

Introduction To SWF

A growing number of applications are relying on asynchronous and distributed processing. The scalability of such applications is the primary motivation for using this approach. By designing autonomous distributed components, developers have the flexibility to deploy and scale out parts of the application independently if the load on the application increases. Another motivation is the availability of cloud services. As application developers start taking advantage of cloud computing, they have a need to combine their existing on-premises assets with additional cloud-based assets. Yet another motivation for the asynchronous and distributed approach is the inherent distributed nature of the process being modeled by the application; for example, the automation of an order-fulfillment business process may span several systems and human tasks.

Developing such applications can be complicated. It requires that you coordinate the execution of multiple distributed components and deal with the increased latencies and unreliability inherent in remote communication. To accomplish this, you would typically need to write complicated infrastructure involving message queues and databases, along with the complex logic to synchronize them.

The Amazon Simple Workflow Service (Amazon SWF) makes it easier to develop asynchronous and distributed applications by providing a programming model and infrastructure for coordinating distributed components and maintaining their execution state in a reliable way. By relying on Amazon SWF, you are freed to focus on building the aspects of your application that differentiate it.

Simple Workflow Concepts

The basic concepts necessary for understanding Amazon SWF workflows are introduced below and are explained further in the subsequent sections of this guide. The following discussion is a high-level overview of the structure and components of a workflow.

The fundamental concept in Amazon SWF is the workflow. A workflow is a set of activities that carry out some objective, together with logic that coordinates the activities. For example, a workflow could receive a customer order and take whatever actions are necessary to fulfill it. Each workflow runs in an AWS resource called a domain, which controls the workflow’s scope. An AWS account can have multiple domains, each of which can contain multiple workflows, but workflows in different domains can’t interact.

When designing an Amazon SWF workflow, you precisely define each of the required activities. You then register each activity with Amazon SWF as an activity type. When you register the activity, you provide information such as a name and version, and some timeout values based on how long you expect the activity to take. For example, a customer may have an expectation that an order will ship within 24 hours. Such expectations would inform the timeout values that you specify when registering your activities.

In the process of carrying out the workflow, some activities may need to be performed more than once, perhaps with varying inputs. For example, in a customer-order workflow, you might have an activity that handles purchased items. If the customer purchases multiple items, then this activity would have to run multiple times. Amazon SWF has the concept of an activity task that represents one invocation of an activity. In our example, the processing of each item would be represented by a single activity task.

An activity worker is a program that receives activity tasks, performs them, and provides results back. Note that the task itself might actually be performed by a person, in which case the person would use the activity worker software for the receipt and disposition of the task. An example might be a statistical analyst, who receives sets of data, analyzes them, and then sends back the analysis.

Activity tasks—and the activity workers that perform them—can run synchronously or asynchronously. They can be distributed across multiple computers, potentially in different geographic regions, or they can all run on the same computer. Different activity workers can be written in different programming languages and run on different operating systems. For example, one activity worker might be running on a desktop computer in Asia, whereas a different activity worker might be running on a hand-held computer device in North America.

The coordination logic in a workflow is contained in a software program called a decider. The decider schedules activity tasks, provides input data to the activity workers, processes events that arrive while the workflow is in progress, and ultimately ends (or closes) the workflow when the objective has been completed.

The role of the Amazon SWF service is to function as a reliable central hub through which data is exchanged between the decider, the activity workers, and other relevant entities such as the person administering the workflow. Amazon SWF also maintains the state of each workflow execution, which saves your application from having to store the state in a durable way.

The decider directs the workflow by receiving decision tasks from Amazon SWF and responding back to Amazon SWF with decisions. A decision represents an action or set of actions which are the next steps in the workflow. A typical decision would be to schedule an activity task. Decisions can also be used to set timers to delay the execution of an activity task, to request cancellation of activity tasks already in progress, and to complete or close the workflow.

The mechanism by which both the activity workers and the decider receive their tasks (activity tasks and decision tasks respectively) is by polling the Amazon SWF service.

Amazon SWF informs the decider of the state of the workflow by including, with each decision task, a copy of the current workflow execution history. The workflow execution history is composed of events, where an event represents a significant change in the state of the workflow execution. Examples of events would be the completion of a task, notification that a task has timed out, or the expiration of a timer that was set earlier in the workflow execution. The history is a complete, consistent, and authoritative record of the workflow’s progress.

Amazon SWF access control uses AWS Identity and Access Management (IAM), which allows you to provide access to AWS resources in a controlled and limited way that doesn’t expose your access keys. For example, you can allow a user to access your account, but only to run certain workflows in a particular domain.

Workflow Execution

Bringing together the ideas discussed in the preceding sections, here is an overview of the steps to develop and run a workflow in Amazon SWF:

  • Write activity workers that implement the processing steps in your workflow.
  • Write a decider to implement the coordination logic of your workflow.
  • Register your activities and workflow with Amazon SWF.
  • You can do this step programmatically or by using the AWS Management Console.
  • Start your activity workers and decider.
  • These actors can run on any computing device that can access an Amazon SWF endpoint. For example, you could use compute instances in the cloud, such as Amazon Elastic Compute Cloud (Amazon EC2); servers in your data center; or even a mobile device, to host a decider or activity worker. Once started, the decider and activity workers should start polling Amazon SWF for tasks.
  • Start one or more executions of your workflow.
  • Executions can be initiated either programmatically or via the AWS Management Console.
  • Each execution runs independently and you can provide each with its own set of input data. When an execution is started, Amazon SWF schedules the initial decision task. In response, your decider begins generating decisions which initiate activity tasks. Execution continues until your decider makes a decision to close the execution.
  • View workflow executions using the AWS Management Console.
  • You can filter and view complete details of running as well as completed executions. For example, you can select an open execution to see which tasks have completed and what their results were.
  • Document Conventions

 

Taken from https://docs.aws.amazon.com/amazonswf/latest/developerguide/swf-dg-intro-to-swf.html

So after reading that we now know that there are a few things we should/would need to implements ourselves, we will look at each of these now

Initiator

This is not official lingo I am just coining this phrase, but anyway in my lingo the initiator is the place that registers the domain, the activities, the workflow and starts the workers/decider. There is quite a lot of boiler plate code here, so its best to just examine the code. One important fact is that when you start the workflow you may pass some state in using the Input field, which can hold serialized data to send to the workers.

Here is the code

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Amazon;
using Amazon.SimpleWorkflow;
using Amazon.SimpleWorkflow.Model;
using System.Threading;

namespace SwfInitiator
{
    class Program
    {
        static string domainName = "SwfDemoDomain";
        static IAmazonSimpleWorkflow SwfClient = AWSClientFactory.CreateAmazonSimpleWorkflowClient();

        public static void Main(string[] args)
        {
            Console.Title = "INITIATOR";

            string workflowName = "SwfDemo Workflow";

            // Setup
            RegisterDomain();
            RegisterActivity("Activity1A", "Activity1");
            RegisterActivity("Activity1B", "Activity1");
            RegisterActivity("Activity2", "Activity2");
            RegisterWorkflow(workflowName);

            //// Launch workers to service Activity1A and Activity1B
            ////  This is acheived by sharing same tasklist name (i.e.) "Activity1"
            StartProcess(@"..\..\..\Worker\bin\Debug\SwfWorker", new[] { "Activity1" });
            StartProcess(@"..\..\..\Worker\bin\Debug\SwfWorker", new[] { "Activity1" });

            //// Launch Workers for Activity2
            StartProcess(@"..\..\..\Worker\bin\Debug\SwfWorker", new[] { "Activity2" });
            StartProcess(@"..\..\..\Worker\bin\Debug\SwfWorker", new[] { "Activity2" });

            //// Start the Deciders, which defines the structure/flow of Workflow
            StartProcess(@"..\..\..\Decider\bin\Debug\SwfDecider", null);
            Thread.Sleep(1000);

            //Start the workflow
            Task.Run(() => StartWorkflow(workflowName));

            Console.Read();
        }

        static void StartProcess(string processLocation, string[] args)
        {
            var p = new System.Diagnostics.Process();
            p.StartInfo.FileName = processLocation;
            if (args != null)
                p.StartInfo.Arguments = String.Join(" ", args);
            p.StartInfo.RedirectStandardOutput = false;
            p.StartInfo.UseShellExecute = true;
            p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
            p.Start();
        }

        static void RegisterDomain()
        {
            // Register if the domain is not already registered.
            var listDomainRequest = new ListDomainsRequest()
            {
                RegistrationStatus = RegistrationStatus.REGISTERED
            };

            if (SwfClient.ListDomains(listDomainRequest).DomainInfos.Infos.FirstOrDefault(
                                                      x => x.Name == domainName) == null)
            {
                var request = new RegisterDomainRequest()
                {
                    Name = domainName,
                    Description = "Swf Demo",
                    WorkflowExecutionRetentionPeriodInDays = "1"
                };

                Console.WriteLine("INITIATOR: Created Domain - " + domainName);
                try
                {
                    SwfClient.RegisterDomain(request);
                }
                catch(DomainAlreadyExistsException dex)
                {

                }
            }
        }

        static void RegisterActivity(string name, string tasklistName)
        {
            // Register activities if it is not already registered
            var listActivityRequest = new ListActivityTypesRequest()
            {
                Domain = domainName,
                Name = name,
                RegistrationStatus = RegistrationStatus.REGISTERED
            };

            if (SwfClient.ListActivityTypes(listActivityRequest).ActivityTypeInfos.TypeInfos.FirstOrDefault(
                                          x => x.ActivityType.Version == "2.0") == null)
            {
                var request = new RegisterActivityTypeRequest()
                {
                    Name = name,
                    Domain = domainName,
                    Description = "Swf Demo Activities",
                    Version = "2.0",
                    DefaultTaskList = new TaskList() { Name = tasklistName },//Worker poll based on this
                    DefaultTaskScheduleToCloseTimeout = "300",
                    DefaultTaskScheduleToStartTimeout = "150",
                    DefaultTaskStartToCloseTimeout = "450",
                    DefaultTaskHeartbeatTimeout = "NONE",
                };
                try
                {

                }
                catch(TypeAlreadyExistsException tex)
                {
                    SwfClient.RegisterActivityType(request);
                }
                Console.WriteLine($"INITIATOR: Created Activity Name - {request.Name}");
            }
        }

        static void RegisterWorkflow(string name)
        {
            // Register workflow type if not already registered
            var listWorkflowRequest = new ListWorkflowTypesRequest()
            {
                Name = name,
                Domain = domainName,
                RegistrationStatus = RegistrationStatus.REGISTERED
            };

            if (SwfClient.ListWorkflowTypes(listWorkflowRequest)
                .WorkflowTypeInfos
                .TypeInfos
                .FirstOrDefault(x => x.WorkflowType.Version == "2.0") == null)
            {
                var request = new RegisterWorkflowTypeRequest()
                {
                    DefaultChildPolicy = ChildPolicy.TERMINATE,
                    DefaultExecutionStartToCloseTimeout = "300",
                    DefaultTaskList = new TaskList()
                    {
                        Name = "SwfDemo" // Decider need to poll for this task
                    },
                    DefaultTaskStartToCloseTimeout = "150",
                    Domain = domainName,
                    Name = name,
                    Version = "2.0"
                };
                try
                {

                }
                catch(TypeAlreadyExistsException tex)
                {
                    SwfClient.RegisterWorkflowType(request);
                }

                Console.WriteLine($"INITIATOR: Registerd Workflow Name - {request.Name}");
            }
        }

        static void StartWorkflow(string name)
        {
            string workflowID = $"Swf DemoID - {DateTime.Now.Ticks.ToString()}";
            SwfClient.StartWorkflowExecution(new StartWorkflowExecutionRequest()
            {
                Input = "{\"inputparam1\":\"value1\"}", // Serialize input to a string
                WorkflowId = workflowID,
                Domain = domainName,
                WorkflowType = new WorkflowType()
                {
                    Name = name,
                    Version = "2.0"
                }
            });
            Console.WriteLine($"INITIATOR: Workflow Instance created ID={workflowID}");
        }
    }
}

 

Decider

So the decider is the piece that will schedule activities for workers, it is also responsible for listening to responses of workers, and working out how to act on this information. It does this by examining the history of DecisionTaskEvents which it gets via polling the SWF engine. In these historical events, it can dig deeper to deduce if certain activities have completed from workers, and if it deems so may complete the workflow, or schedule more tasks. Again with this one the code makes a better job of explaining things than I do

 

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon;
using Amazon.SimpleWorkflow;
using Amazon.SimpleWorkflow.Model;

namespace SwfDeciderDecider
{
    class Program
    {
        static string domainName = "SwfDemoDomain";
        static IAmazonSimpleWorkflow SwfDeciderClient =
                    AWSClientFactory.CreateAmazonSimpleWorkflowClient();

        public static void Main(string[] args)
        {
            Console.Title = "DECIDER";
            // Start the Deciders, which defines the structure/flow of Workflow
            Task.Run(() => Decider());
            Console.Read();
        }


        // Simple logic
        //  Creates four activities at the begining
        //  Waits for them to complete and completes the workflow
        static void Decider()
        {
            int activityCount = 0; // This refers to total number of activities per workflow
            while (true)
            {
                Console.WriteLine("DECIDER: Polling for decision task ...");
                var request = new PollForDecisionTaskRequest()
                {
                    Domain = domainName,
                    TaskList = new TaskList() { Name = "SwfDemo" }
                };

                var response = SwfDeciderClient.PollForDecisionTask(request);
                if (response.DecisionTask.TaskToken == null)
                {
                    Console.WriteLine("DECIDER: NULL");
                    continue;
                }

                int completedActivityTaskCount = 0, totalActivityTaskCount = 0;
                foreach (HistoryEvent e in response.DecisionTask.Events)
                {
                    Console.WriteLine($"DECIDER: EventType - {e.EventType}" +
                        $", EventId - {e.EventId}");
                    if (e.EventType == "ActivityTaskCompleted")
                        completedActivityTaskCount++;
                    if (e.EventType.Value.StartsWith("Activity"))
                        totalActivityTaskCount++;
                }
                Console.WriteLine($"completedCount={completedActivityTaskCount}");

                var decisions = new List<Decision>();
                if (totalActivityTaskCount == 0) // Create this only at the begining
                {
                    ScheduleActivity("Activity1A", decisions);
                    ScheduleActivity("Activity1B", decisions);
                    ScheduleActivity("Activity2", decisions);
                    ScheduleActivity("Activity2", decisions);
                    activityCount = 4;
                }
                else if (completedActivityTaskCount == activityCount)
                {
                    var decision = new Decision()
                    {
                        DecisionType = DecisionType.CompleteWorkflowExecution,
                        CompleteWorkflowExecutionDecisionAttributes =
                          new CompleteWorkflowExecutionDecisionAttributes
                          {
                              Result = "{\"Result\":\"WF Complete!\"}"
                          }
                    };
                    decisions.Add(decision);

                    Console.WriteLine("DECIDER: WORKFLOW COMPLETE");
                }
                var respondDecisionTaskCompletedRequest =
                    new RespondDecisionTaskCompletedRequest()
                    {
                        Decisions = decisions,
                        TaskToken = response.DecisionTask.TaskToken
                    };
                SwfDeciderClient.RespondDecisionTaskCompleted(respondDecisionTaskCompletedRequest);
            }
        }

        static void ScheduleActivity(string name, List<Decision> decisions)
        {
            var decision = new Decision()
            {
                DecisionType = DecisionType.ScheduleActivityTask,
                ScheduleActivityTaskDecisionAttributes =  
                  new ScheduleActivityTaskDecisionAttributes()
                  {
                      ActivityType = new ActivityType()
                      {
                          Name = name,
                          Version = "2.0"
                      },
                      ActivityId = name + "-" + System.Guid.NewGuid().ToString(),
                      Input = "{\"activityInput1\":\"value1\"}"
                  }
            };
            Console.WriteLine($"DECIDER: ActivityId={decision.ScheduleActivityTaskDecisionAttributes.ActivityId}");
            decisions.Add(decision);
        }
    }
}

Worker

So as stated above the worker should poll for ActivityTasks, which they can do using the PollForActivityTaskRequest, if they get one that matches their query, they should do the work according to what they can do with the PollForActivityTaskResponse, which has an ActivityTask with a Input on it which can contain serialized data for the worker. Once the worker is done they submit a RespondActivityTaskCompletedRequest back to the SWF engine using the SwfClient.RespondActivityTaskCompleted(….) method

This will certainly be easier to understand with some code, which is shown here

using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.SimpleWorkflow;
using Amazon.SimpleWorkflow.Model;

namespace SwfWorker
{
    class Program
    {
        static string domainName = "SwfDemoDomain";
        static IAmazonSimpleWorkflow SwfClient = AWSClientFactory.CreateAmazonSimpleWorkflowClient();

        public static void Main(string[] args)
        {
            string tasklistName = args[0];
            Console.Title = tasklistName.ToUpper();
            Task.Run(() => Worker(tasklistName));
            Console.Read();
        }


        static void Worker(string tasklistName)
        {
            string prefix = string.Format("WORKER {0}:{1:x} ", tasklistName,
                                  System.Threading.Thread.CurrentThread.ManagedThreadId);
            while (true)
            {
                Console.WriteLine($"{prefix} : Polling for activity task ...");
                var pollForActivityTaskRequest =
                    new PollForActivityTaskRequest()
                    {
                        Domain = domainName,
                        TaskList = new TaskList()
                        {
                            // Poll only the tasks assigned to me
                            Name = tasklistName
                        }
                    };

                var pollForActivityTaskResponse =        
                    SwfClient.PollForActivityTask(pollForActivityTaskRequest);

                if (pollForActivityTaskResponse.ActivityTask.ActivityId == null)
                {
                    Console.WriteLine($"{prefix} : NULL");
                }
                else
                {
                    Console.WriteLine($"{prefix} : saw Input {pollForActivityTaskResponse.ActivityTask.Input}");

                    var respondActivityTaskCompletedRequest = new RespondActivityTaskCompletedRequest()
                        {
                            Result = "{\"activityResult1\":\"Result Value1\"}",
                            TaskToken = pollForActivityTaskResponse.ActivityTask.TaskToken
                        };

                    var respondActivityTaskCompletedResponse =
                        SwfClient.RespondActivityTaskCompleted(respondActivityTaskCompletedRequest);
                    Console.WriteLine($"{prefix} : Activity task completed. ActivityId - " +
                        pollForActivityTaskResponse.ActivityTask.ActivityId);
                }
            }
        }


    }
}

SWF Console

So the console has full support for things like

  • Registering domains
  • Register new Activity type
  • Register new Workflow type
  • Listing/re-run executions

But as we just saw a lot of this can be done in code. The code I just presented in this post, is running on my own PC, but you could imagine this could run anywhere that has AWS endpoint access

Here is a taster of what the SWF console looks like

image

Running the demo

So if you were to open the demo code, and ensure you have your correct user profile set up in the App.Config variables, and have the Initiator set as the start project, and run it you should see some output like this, with the processes all started in their own windows

Initiator

INITIATOR: Created Domain – SwfDemoDomain
INITIATOR: Created Activity Name – Activity1A
INITIATOR: Created Activity Name – Activity1B
INITIATOR: Created Activity Name – Activity2
INITIATOR: Registerd Workflow Name – SwfDemo Workflow
INITIATOR: Workflow Instance created ID=Swf DeomoID – 636779091969304227

Worker typical output

WORKER Activity1:3  : saw Input {“activityInput1″:”value1”}
WORKER Activity1:3  : Activity task completed. ActivityId – Activity1B-2c1cec6d-90c8-4146-8c3a-1ba15fed9628
WORKER Activity1:3  : Polling for activity task …
WORKER Activity1:3  : NULL
WORKER Activity1:3  : Polling for activity task …
WORKER Activity1:3  : NULL
WORKER Activity1:3  : Polling for activity task …
WORKER Activity1:3  : NULL
WORKER Activity1:3  : Polling for activity task …
Decider

DECIDER: Polling for decision task …
DECIDER: EventType – WorkflowExecutionStarted, EventId – 1
DECIDER: EventType – DecisionTaskScheduled, EventId – 2
DECIDER: EventType – DecisionTaskStarted, EventId – 3
completedCount=0
DECIDER: ActivityId=Activity1A-1221249f-1c3e-4f4e-baa3-7f2ae4db12d1
DECIDER: ActivityId=Activity1B-2c1cec6d-90c8-4146-8c3a-1ba15fed9628
DECIDER: ActivityId=Activity2-0721eb07-9790-46f5-93c9-142fcc572555
DECIDER: ActivityId=Activity2-51dd0fa7-45de-43b0-b810-8185e12f5ab3
DECIDER: Polling for decision task …
DECIDER: EventType – WorkflowExecutionStarted, EventId – 1
DECIDER: EventType – DecisionTaskScheduled, EventId – 2
DECIDER: EventType – DecisionTaskStarted, EventId – 3
DECIDER: EventType – DecisionTaskCompleted, EventId – 4
DECIDER: EventType – ActivityTaskScheduled, EventId – 5
DECIDER: EventType – ActivityTaskScheduled, EventId – 6
DECIDER: EventType – ActivityTaskScheduled, EventId – 7
DECIDER: EventType – ActivityTaskScheduled, EventId – 8
DECIDER: EventType – ActivityTaskStarted, EventId – 9
DECIDER: EventType – ActivityTaskStarted, EventId – 10
DECIDER: EventType – ActivityTaskStarted, EventId – 11
DECIDER: EventType – ActivityTaskStarted, EventId – 12
DECIDER: EventType – ActivityTaskCompleted, EventId – 13
DECIDER: EventType – DecisionTaskScheduled, EventId – 14
DECIDER: EventType – ActivityTaskCompleted, EventId – 15
DECIDER: EventType – ActivityTaskCompleted, EventId – 16
DECIDER: EventType – ActivityTaskCompleted, EventId – 17
DECIDER: EventType – DecisionTaskStarted, EventId – 18
completedCount=4
DECIDER: WORKFLOW COMPLETE
DECIDER: Polling for decision task …

Advertisements
AWS

AWS Simple Email Service (SES)

What are we talking about this time?

Last time we talked about Step Functions.This time we will be talking about Simple Email Service (SES). I don’t want to cover everything about this service, but I do want to show you how you can use it so send emails as a bare minimum.

Initial setup

If you did not read the very first part of this series of posts, I urge you to go and read that one now as it shows you how to get started with AWS, and create an IAM user : https://sachabarbs.wordpress.com/2018/08/30/aws-initial-setup/

Where is the code

The code for this post can be found here in GitHub : https://github.com/sachabarber/AWS/tree/master/AppServices/SES

What are we talking about this time?

Ok so as I stated above this time we are going to be talking about SES, which is going to be fairly self contained, and a small post this time to be honest. I just want to show how we can use SES to send mails from our own apps.

Setting It Up And Sending An Email

Before we start it is important to note that the SES service is limited to a few regions. So my normal EU-WEST2 is NOT supported. So I have to use EU-WEST1. 

So the first step is to setup a verified email which you can do in the SES console here

image

Once you have done that you can use the verification email that gets sent to the email address you used to complete the verification process. Then you should be good to use this email as a sender for SES. You can read more about this process here : https://docs.aws.amazon.com/ses/latest/DeveloperGuide/setting-up-email.html

But assuming you have done this step, it really is as simple as making sure you are using the correct region, and then using the verified email you just created and using some code like this

using Amazon;
using System;
using System.Collections.Generic;
using System.Configuration;
using Amazon.SimpleEmail;
using Amazon.SimpleEmail.Model;
using Amazon.Runtime;

namespace SESSender
{
    class Program
    {
        // Set the sender's email address here : AWSVerifiedEmail
        static readonly string senderAddress = ";

        // Set the receiver's email address here.
        static readonly string receiverAddress = "";

        static void Main(string[] args)
        {
            if (CheckRequiredFields())
            {

                using (var client = new AmazonSimpleEmailServiceClient(RegionEndpoint.EUWest1))
                {
                    var sendRequest = new SendEmailRequest
                    {
                        Source = senderAddress,
                        Destination = new Destination { ToAddresses = new List<string> { receiverAddress } },
                        Message = new Message
                        {
                            Subject = new Content("Sample Mail using SES"),
                            Body = new Body { Text = new Content("Sample message content.") }
                        }
                    };
                    try
                    {
                        Console.WriteLine("Sending email using AWS SES...");
                        var response = client.SendEmail(sendRequest);
                        Console.WriteLine("The email was sent successfully.");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("The email was not sent.");
                        Console.WriteLine("Error message: " + ex.Message);

                    }
                }
            }

            Console.Write("Press any key to continue...");
            Console.ReadKey();
        }

        static bool CheckRequiredFields()
        {
            var appConfig = ConfigurationManager.AppSettings;

            if (string.IsNullOrEmpty(appConfig["AWSProfileName"]))
            {
                Console.WriteLine("AWSProfileName was not set in the App.config file.");
                return false;
            }
            if (string.IsNullOrEmpty(senderAddress))
            {
                Console.WriteLine("The variable senderAddress is not set.");
                return false;
            }
            if (string.IsNullOrEmpty(receiverAddress))
            {
                Console.WriteLine("The variable receiverAddress is not set.");
                return false;
            }
            return true;
        }
    }
}

Remember this service IS NOT available in all regions, so make sure you have the correct region set.

Anyway here is the result of sending the email

image

 

SMTP

To use SMTP instead you need to setup the SMTP user in SES console

image

Once you have that you can then just use code something more like this

 

using System;
using System.Net;
using System.Net.Mail;
using System.Configuration;


namespace SESSMTPSample1
{
    class Program
    {
        // Set the sender's email address here.
        static string senderAddress = null;

        // Set the receiver's email address here.
        static string receiverAddress = null;
        
        // Set the SMTP user name in App.config 
        static string smtpUserName = null;

        // Set the SMTP password in App.config 
        static string smtpPassword = null;

        static string host = "email-smtp.eu-west-1.amazonaws.com";

        static int port = 587;

        static void Main(string[] args)
        {
            if (CheckRequiredFields())
            {
                var smtpClient = new SmtpClient(host, port);
                smtpClient.EnableSsl = true;
                smtpClient.Credentials = new NetworkCredential(smtpUserName, smtpPassword);

                var message = new MailMessage(
                                from: senderAddress,
                                to: receiverAddress,
                                subject: "Sample email using SMTP Interface",
                                body: "Sample email.");

                try
                {
                    Console.WriteLine("Sending email using SMTP interface...");
                    smtpClient.Send(message);
                    Console.WriteLine("The email was sent successfully.");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("The email was not sent.");
                    Console.WriteLine("Error message: " + ex.Message);
                }
            }

            Console.Write("Press any key to continue...");
            Console.ReadKey(); 
        }

        static bool CheckRequiredFields()
        {
            var appConfig = ConfigurationManager.AppSettings;

            smtpUserName = appConfig["AwsSesSmtpUserName"];
            if (string.IsNullOrEmpty(smtpUserName))
            {
                Console.WriteLine("AwsSesSmtpUserName is not set in the App.config file.");
                return false;
            }

            smtpPassword = appConfig["AwsSesSmtpPassword"];
            if (string.IsNullOrEmpty(smtpPassword))
            {
                Console.WriteLine("AwsSesSmtpPassword is not set in the App.config file.");
                return false;
            }
            if (string.IsNullOrEmpty(senderAddress))
            {
                Console.WriteLine("The variable senderAddress is not set.");
                return false;
            }
            if (string.IsNullOrEmpty(receiverAddress))
            {
                Console.WriteLine("The variable receiverAddress is not set.");
                return false;
            }
 
            return true;
        }
    }
}

 

And that is all I really wanted to show in this short post. Next time we will likely dip back into Compute stuff, where we look at AWS SWF