Repository: gruntwork-io/cloud-nuke
Pull request #638 (2024-02-15 21:47:44)

Implement SES resources types
<!-- Prepend '[WIP]' to the title if this PR is still a work-in-progress. Remove it when it is ready for review! -->

## Description

Fixes https://github.com/gruntwork-io/cloud-nuke/issues/447.

<!-- Description of the changes introduced by this PR. -->

## TODOs

Read the [Gruntwork contribution guidelines](https://gruntwork.notion.site/Gruntwork-Coding-Methodology-02fdcd6e4b004e818553684760bf691e).

- [ ] Update the docs.
- [ ] Run the relevant tests successfully, including pre-commit checks.
- [ ] Ensure any 3rd party code adheres with our [license policy](https://www.notion.so/gruntwork/Gruntwork-licenses-and-open-source-usage-policy-f7dece1f780341c7b69c1763f22b1378) or delete this line if its not applicable.
- [ ] Include release notes. If this PR is backward incompatible, include a migration guide.
- [ ] Attention Grunts - if this PR adds support for a new resource, ensure the `nuke_sandbox` and `nuke_phxdevops` jobs in `.circleci/config.yml` have been updated with appropriate exclusions (either directly in the job or via the `.circleci/nuke_config.yml` file) to prevent nuking IAM roles, groups, resources, etc that are important for the test accounts.


## Release Notes (draft)

<!-- One-line description of the PR that can be included in the final release notes. -->
Added / Removed / Updated [X].

### Migration Guide

<!-- Important: If you made any backward incompatible changes, then you must write a migration guide! -->




Patch:
diff --git a/README.md b/README.md
index f67d91d..d1bc456 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,11 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
 | Security Hub            | Hubs                                                     |
 | Security Hub            | Members                                                  |
 | Security Hub            | Administrators                                           |
+| SES                     | SES configuration set                                    |
+| SES                     | SES email template                                       |
+| SES                     | SES Identity                                             |
+| SES                     | SES receipt rule set                                     |
+| SES                     | SES receipt filter                                       |
 | AWS Certificate Manager | Certificates                                             |
 | CodeDeploy              | Applications                                             |
 
@@ -307,74 +312,74 @@ resources.
 package main
 
 import (
-  "context"
-  "fmt"
-  "time"
-
-  "github.com/aws/aws-sdk-go/aws"
-  nuke_aws "github.com/gruntwork-io/cloud-nuke/aws"
-  nuke_config "github.com/gruntwork-io/cloud-nuke/config"
-  "github.com/gruntwork-io/cloud-nuke/externalcreds"
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	nuke_aws "github.com/gruntwork-io/cloud-nuke/aws"
+	nuke_config "github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/externalcreds"
 )
 
 func main() {
-  // You can scan multiple regions at once, or just pass a single region for speed
-  targetRegions := []string{"us-east-1", "us-west-1", "us-west-2"}
-  excludeRegions := []string{}
-  // You can simultaneously target multiple resource types as well
-  resourceTypes := []string{"ec2", "vpc"}
-  excludeResourceTypes := []string{}
-  // excludeAfter is parsed identically to the --older-than flag
-  excludeAfter := time.Now()
-  // an optional start time- can pass null if the filter is not required
-  includeAfter := time.Now().AddDate(-1, 0, 0)
-
-  // Any custom settings you want
-  myCustomConfig := &aws.Config{}
-  myCustomConfig.WithMaxRetries(3)
-  myCustomConfig.WithLogLevel(aws.LogDebugWithRequestErrors)
-  // Optionally, set custom credentials
-  // myCustomConfig.WithCredentials()
-
-  // Be sure to set your config prior to calling any library methods such as NewQuery
-  externalcreds.Set(myCustomConfig)
-  // this config can be configured to add include/exclude rule to filter the resources- for all resources pass an empty struct
-  nukeConfig := nuke_config.Config{}
-
-  // NewQuery is a convenience method for configuring parameters you want to pass to your resource search
-  query, err := nuke_aws.NewQuery(
-    targetRegions,
-    excludeRegions,
-    resourceTypes,
-    excludeResourceTypes,
-    &excludeAfter,
-    &includeAfter,
-    false,
-  )
-  if err != nil {
-    fmt.Println(err)
-  }
-
-  // GetAllResources still returns *AwsAccountResources, but this struct has been extended with several
-  // convenience methods for quickly determining if resources exist in a given region
-  accountResources, err := nuke_aws.GetAllResources(context.Background(), query, nukeConfig)
-  if err != nil {
-    fmt.Println(err)
-  }
-  // You can call GetRegion to examine a single region's resources
-  usWest1Resources := accountResources.GetRegion("us-west-1")
-  // Then interrogate them with the new methods:
-  // Count the number of any resource type within the region
-  countOfEc2InUsWest1 := usWest1Resources.CountOfResourceType("ec2")
-  fmt.Printf("countOfEc2InUsWest1: %d\n", countOfEc2InUsWest1)
-  // countOfEc2InUsWest1: 2
-  fmt.Printf("usWest1Resources.ResourceTypePresent(\"ec2\"):%b\n", usWest1Resources.ResourceTypePresent("ec2"))
-  // usWest1Resources.ResourceTypePresent("ec2"): true
-  // Get all the resource identifiers for a given resource type
-  // In this example, we're only looking for ec2 instances
-  resourceIds := usWest1Resources.IdentifiersForResourceType("ec2")
-  fmt.Printf("resourceIds: %s", resourceIds)
-  // resourceIds:  [i-0c5d16c3ef28dda24 i-09d9739e1f4d27814]
+	// You can scan multiple regions at once, or just pass a single region for speed
+	targetRegions := []string{"us-east-1", "us-west-1", "us-west-2"}
+	excludeRegions := []string{}
+	// You can simultaneously target multiple resource types as well
+	resourceTypes := []string{"ec2", "vpc"}
+	excludeResourceTypes := []string{}
+	// excludeAfter is parsed identically to the --older-than flag
+	excludeAfter := time.Now()
+	// an optional start time- can pass null if the filter is not required
+	includeAfter := time.Now().AddDate(-1, 0, 0)
+
+	// Any custom settings you want
+	myCustomConfig := &aws.Config{}
+	myCustomConfig.WithMaxRetries(3)
+	myCustomConfig.WithLogLevel(aws.LogDebugWithRequestErrors)
+	// Optionally, set custom credentials
+	// myCustomConfig.WithCredentials()
+
+	// Be sure to set your config prior to calling any library methods such as NewQuery
+	externalcreds.Set(myCustomConfig)
+	// this config can be configured to add include/exclude rule to filter the resources- for all resources pass an empty struct
+	nukeConfig := nuke_config.Config{}
+
+	// NewQuery is a convenience method for configuring parameters you want to pass to your resource search
+	query, err := nuke_aws.NewQuery(
+		targetRegions,
+		excludeRegions,
+		resourceTypes,
+		excludeResourceTypes,
+		&excludeAfter,
+		&includeAfter,
+		false,
+	)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	// GetAllResources still returns *AwsAccountResources, but this struct has been extended with several
+	// convenience methods for quickly determining if resources exist in a given region
+	accountResources, err := nuke_aws.GetAllResources(context.Background(), query, nukeConfig)
+	if err != nil {
+		fmt.Println(err)
+	}
+	// You can call GetRegion to examine a single region's resources
+	usWest1Resources := accountResources.GetRegion("us-west-1")
+	// Then interrogate them with the new methods:
+	// Count the number of any resource type within the region
+	countOfEc2InUsWest1 := usWest1Resources.CountOfResourceType("ec2")
+	fmt.Printf("countOfEc2InUsWest1: %d\n", countOfEc2InUsWest1)
+	// countOfEc2InUsWest1: 2
+	fmt.Printf("usWest1Resources.ResourceTypePresent(\"ec2\"):%b\n", usWest1Resources.ResourceTypePresent("ec2"))
+	// usWest1Resources.ResourceTypePresent("ec2"): true
+	// Get all the resource identifiers for a given resource type
+	// In this example, we're only looking for ec2 instances
+	resourceIds := usWest1Resources.IdentifiersForResourceType("ec2")
+	fmt.Printf("resourceIds: %s", resourceIds)
+	// resourceIds:  [i-0c5d16c3ef28dda24 i-09d9739e1f4d27814]
 }
 
 ```
@@ -541,6 +546,11 @@ of the file that are supported are listed here.
 | opensearchdomain            | OpenSearchDomain             | ✅ (Domain Name)                       | ✅ (First Seen Tag Time)             | ❌    |
 | redshift                    | Redshift                     | ✅ (Cluster Identifier)                | ✅ (Creation Time)                   | ❌    |
 | s3                          | s3                           | ✅ (Bucket Name)                       | ✅ (Creation Time)                   | ✅    |
+| ses-configuration-set       | SesConfigurationset          | ✅ (Configuration set name)            | ❌                                   | ❌    |
+| ses-email-template          | SesEmailTemplates            | ✅ (Template Name)                     | ✅ (Creation Time)                   | ❌    |
+| ses-identity                | SesIdentity                  | ✅ (Identity -Mail/Domain)             | ❌                                   | ❌    |
+| ses-receipt-rule-set        | SesReceiptRuleSet            | ✅ (Receipt Rule Set Name)             | ✅ (Creation Time)                   | ❌    |
+| ses-receipt-filter          | SesReceiptFilter             | ✅ (Receipt Filter Name)               | ❌                                   | ❌    |
 | snstopic                    | SNS                          | ✅ (Topic Name)                        | ✅ (First Seen Tag Time)             | ❌    |
 | sqs                         | SQS                          | ✅ (Queue Name)                        | ✅ (Creation Time)                   | ❌    |
 | sagemaker-notebook-smni     | SageMakerNotebook            | ✅ (Notebook Instnace Name)            | ✅ (Creation Time)                   | ❌    |
@@ -747,6 +757,11 @@ job for this repo has been configured to:
 
 See `.circleci/config.yml` for details.
 
+## Nukable error statuses
+You'll encounter any of the following statuses when attempting to nuke resources, and here's what each status means:
+- `error:INSUFFICIENT_PERMISSION` : You don't have enough permission to nuke the resource.
+- `error:DIFFERENT_OWNER` : You are attempting to nuke a resource for which you are not the owner.
+
 ## License
 
 This code is released under the MIT License. See [LICENSE.txt](/LICENSE.txt).
diff --git a/aws/resource_registry.go b/aws/resource_registry.go
index 051e676..89ae28c 100644
--- a/aws/resource_registry.go
+++ b/aws/resource_registry.go
@@ -104,6 +104,11 @@ func getRegisteredRegionalResources() []AwsResource {
 		&resources.SageMakerNotebookInstances{},
 		&resources.SecretsManagerSecrets{},
 		&resources.SecurityHub{},
+		&resources.SesConfigurationSet{},
+		&resources.SesEmailTemplates{},
+		&resources.SesIdentities{},
+		&resources.SesReceiptRule{},
+		&resources.SesReceiptFilter{},
 		&resources.Snapshots{},
 		&resources.SNSTopic{},
 		&resources.SqsQueue{},
diff --git a/aws/resources/base_resource.go b/aws/resources/base_resource.go
index 5c71408..a028abb 100644
--- a/aws/resources/base_resource.go
+++ b/aws/resources/base_resource.go
@@ -3,18 +3,24 @@ package resources
 import (
 	"context"
 	"errors"
+	"fmt"
 
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/util"
 )
 
-// BaseAwsResource This BaseAwsResource struct and its associated methods to serve as a placeholder or template for a resource
-// that is not yet fully implemented within a system or framework.
-// Its purpose is to provide a skeleton structure that adheres to a specific interface or contract expected by the
-// system without containing the actual implementation details.
-type BaseAwsResource struct{}
+// BaseAwsResource struct and its associated methods to serve as a placeholder or template for a resource that is not
+// yet fully implemented within a system or framework. Its purpose is to provide a skeleton structure that adheres to a
+// specific interface or contract expected by the system without containing the actual implementation details.
+type BaseAwsResource struct {
+	// A key-value of identifiers and nukable status
+	Nukables map[string]error
+}
 
-func (umpl *BaseAwsResource) Init(_ *session.Session) {}
+func (umpl *BaseAwsResource) Init(_ *session.Session) {
+	umpl.Nukables = make(map[string]error)
+}
 func (umpl *BaseAwsResource) ResourceName() string {
 	return "not implemented: ResourceName"
 }
@@ -30,6 +36,39 @@ func (umpl *BaseAwsResource) Nuke(_ []string) error {
 func (umpl *BaseAwsResource) GetAndSetIdentifiers(_ context.Context, _ config.Config) ([]string, error) {
 	return nil, errors.New("not implemented: GetAndSetIdentifiers")
 }
-func (umpl *BaseAwsResource) IsNukable(_ string) (bool, error) {
-	return false, errors.New("not implemented yet.")
+
+func (umpl *BaseAwsResource) GetNukableStatus(identifier string) (error, bool) {
+	val, ok := umpl.Nukables[identifier]
+	return val, ok
+}
+
+func (umpl *BaseAwsResource) SetNukableStatus(identifier string, err error) {
+	umpl.Nukables[identifier] = err
+}
+
+// VerifyNukablePermissions performs nukable permission verification for each ID. For each ID, the function is
+// executed, and the result (error or success) is recorded using the SetNukableStatus method, indicating whether
+// the specified action is nukable
+func (umpl *BaseAwsResource) VerifyNukablePermissions(ids []*string, nukableCheckfn func(id *string) error) {
+	for _, id := range ids {
+		// skip if the id is already exists
+		if _, ok := umpl.GetNukableStatus(*id); ok {
+			continue
+		}
+		err := nukableCheckfn(id)
+		umpl.SetNukableStatus(*id, util.TransformAWSError(err))
+	}
+}
+
+func (umpl *BaseAwsResource) IsNukable(identifier string) (bool, error) {
+	err, ok := umpl.Nukables[identifier]
+	if !ok {
+		return false, fmt.Errorf("-")
+	}
+
+	if err != nil {
+		return false, err
+	}
+
+	return true, nil
 }
diff --git a/aws/resources/ses_configuration_set.go b/aws/resources/ses_configuration_set.go
new file mode 100644
index 0000000..38422ec
--- /dev/null
+++ b/aws/resources/ses_configuration_set.go
@@ -0,0 +1,68 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Returns a formatted string of ses-configuration set names
+func (s *SesConfigurationSet) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	// Remove default route table, that will be deleted along with its TransitGateway
+	param := &ses.ListConfigurationSetsInput{}
+
+	result, err := s.Client.ListConfigurationSets(param)
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var setnames []*string
+	for _, set := range result.ConfigurationSets {
+		if configObj.SESConfigurationSet.ShouldInclude(config.ResourceValue{Name: set.Name}) {
+			setnames = append(setnames, set.Name)
+		}
+	}
+
+	return setnames, nil
+}
+
+// Deletes all sets
+func (s *SesConfigurationSet) nukeAll(sets []*string) error {
+	if len(sets) == 0 {
+		logging.Debugf("No SES configuration sets to nuke in region %s", s.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting all SES configuration sets in region %s", s.Region)
+	var deletedSets []*string
+
+	for _, set := range sets {
+		_, err := s.Client.DeleteConfigurationSet(&ses.DeleteConfigurationSetInput{
+			ConfigurationSetName: set,
+		})
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(set),
+			ResourceType: "SES configuration set",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Debugf("[Failed] %s", err)
+		} else {
+			deletedSets = append(deletedSets, set)
+			logging.Debugf("Deleted SES configuration set: %s", *set)
+		}
+	}
+
+	logging.Debugf("[OK] %d SES configuration set(s) deleted in %s", len(deletedSets), s.Region)
+
+	return nil
+}
diff --git a/aws/resources/ses_configuration_set_types.go b/aws/resources/ses_configuration_set_types.go
new file mode 100644
index 0000000..d0e8164
--- /dev/null
+++ b/aws/resources/ses_configuration_set_types.go
@@ -0,0 +1,58 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// SesConfigurationSet - represents all SES configuartion set
+type SesConfigurationSet struct {
+	BaseAwsResource
+	Client sesiface.SESAPI
+	Region string
+	Ids    []string
+}
+
+func (scs *SesConfigurationSet) Init(session *session.Session) {
+	scs.Client = ses.New(session)
+}
+
+// ResourceName - the simple name of the aws resource
+func (scs *SesConfigurationSet) ResourceName() string {
+	return "ses-configuration-set"
+}
+
+// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle
+func (scs *SesConfigurationSet) MaxBatchSize() int {
+	return maxBatchSize
+}
+
+// ResourceIdentifiers - The Ids of the configuration set
+func (scs *SesConfigurationSet) ResourceIdentifiers() []string {
+	return scs.Ids
+}
+
+func (scs *SesConfigurationSet) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := scs.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	scs.Ids = awsgo.StringValueSlice(identifiers)
+	return scs.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (scs *SesConfigurationSet) Nuke(identifiers []string) error {
+	if err := scs.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/ses_email_receiving.go b/aws/resources/ses_email_receiving.go
new file mode 100644
index 0000000..ba51e45
--- /dev/null
+++ b/aws/resources/ses_email_receiving.go
@@ -0,0 +1,127 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Returns a formatted string of receipt rule names
+func (s *SesReceiptRule) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	result, err := s.Client.ListReceiptRuleSets(&ses.ListReceiptRuleSetsInput{})
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var rulesets []*string
+	for _, sets := range result.RuleSets {
+		if configObj.SESReceiptRuleSet.ShouldInclude(config.ResourceValue{
+			Name: sets.Name,
+			Time: sets.CreatedTimestamp,
+		}) {
+			rulesets = append(rulesets, sets.Name)
+		}
+	}
+
+	return rulesets, nil
+}
+
+// Deletes all receipt rules
+func (s *SesReceiptRule) nukeAll(sets []*string) error {
+	if len(sets) == 0 {
+		logging.Debugf("No SES rule sets sets to nuke in region %s", s.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting all SES rule sets in region %s", s.Region)
+	var deletedSets []*string
+
+	for _, set := range sets {
+
+		param := &ses.DeleteReceiptRuleSetInput{
+			RuleSetName: set,
+		}
+		_, err := s.Client.DeleteReceiptRuleSet(param)
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(set),
+			ResourceType: "SES receipt rule set",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Debugf("[Failed] %s", err)
+		} else {
+			deletedSets = append(deletedSets, set)
+			logging.Debugf("Deleted SES receipt rule set: %s", *set)
+		}
+	}
+
+	logging.Debugf("[OK] %d SES receipt rule set(s) deleted in %s", len(deletedSets), s.Region)
+
+	return nil
+}
+
+// ///////////////////////receipt Ip filters //////////////////////////////////////
+
+// Returns a formatted string of ses-identities IDs
+func (s *SesReceiptFilter) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	result, err := s.Client.ListReceiptFilters(&ses.ListReceiptFiltersInput{})
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var filters []*string
+	for _, filter := range result.Filters {
+		if configObj.SESReceiptFilter.ShouldInclude(config.ResourceValue{
+			Name: filter.Name,
+		}) {
+			filters = append(filters, filter.Name)
+		}
+	}
+
+	return filters, nil
+}
+
+// Deletes all filters
+func (s *SesReceiptFilter) nukeAll(filters []*string) error {
+	if len(filters) == 0 {
+		logging.Debugf("No SES receipt filters to nuke in region %s", s.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting all SES receipt  filters in region %s", s.Region)
+	var deletedFilters []*string
+
+	for _, filter := range filters {
+
+		param := &ses.DeleteReceiptFilterInput{
+			FilterName: filter,
+		}
+		_, err := s.Client.DeleteReceiptFilter(param)
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(filter),
+			ResourceType: "SES receipt filter",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Debugf("[Failed] %s", err)
+		} else {
+			deletedFilters = append(deletedFilters, filter)
+			logging.Debugf("Deleted SES receipt filter: %s", *filter)
+		}
+	}
+
+	logging.Debugf("[OK] %d SES receipt receipt filter(s) deleted in %s", len(deletedFilters), s.Region)
+
+	return nil
+}
diff --git a/aws/resources/ses_email_receiving_types.go b/aws/resources/ses_email_receiving_types.go
new file mode 100644
index 0000000..54f23b7
--- /dev/null
+++ b/aws/resources/ses_email_receiving_types.go
@@ -0,0 +1,107 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+type SesReceiptFilter struct {
+	BaseAwsResource
+	Client  sesiface.SESAPI
+	Region  string
+	Ids     []string
+	Nukable map[string]bool
+}
+
+func (sef *SesReceiptFilter) Init(session *session.Session) {
+	sef.Client = ses.New(session)
+	sef.Nukable = map[string]bool{}
+}
+
+// ResourceName - the simple name of the aws resource
+func (sef *SesReceiptFilter) ResourceName() string {
+	return "ses-receipt-filter"
+}
+
+// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle
+func (sef *SesReceiptFilter) MaxBatchSize() int {
+	return maxBatchSize
+}
+
+// ResourceIdentifiers - The Ids of the receipt filter
+func (sef *SesReceiptFilter) ResourceIdentifiers() []string {
+	return sef.Ids
+}
+
+func (sef *SesReceiptFilter) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := sef.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	sef.Ids = awsgo.StringValueSlice(identifiers)
+	return sef.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (sef *SesReceiptFilter) Nuke(identifiers []string) error {
+	if err := sef.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
+
+// SesReceiptRule - represents all ses receipt rules
+type SesReceiptRule struct {
+	BaseAwsResource
+	Client  sesiface.SESAPI
+	Region  string
+	Ids     []string
+	Nukable map[string]bool
+}
+
+func (ser *SesReceiptRule) Init(session *session.Session) {
+	ser.Client = ses.New(session)
+	ser.Nukable = map[string]bool{}
+}
+
+// ResourceName - the simple name of the aws resource
+func (ser *SesReceiptRule) ResourceName() string {
+	return "ses-receipt-rule-set"
+}
+
+// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle
+func (ser *SesReceiptRule) MaxBatchSize() int {
+	return maxBatchSize
+}
+
+// ResourceIdentifiers - The names of the rule set
+func (ser *SesReceiptRule) ResourceIdentifiers() []string {
+	return ser.Ids
+}
+
+func (ser *SesReceiptRule) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := ser.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	ser.Ids = awsgo.StringValueSlice(identifiers)
+	return ser.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (ser *SesReceiptRule) Nuke(identifiers []string) error {
+	if err := ser.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/ses_email_templates.go b/aws/resources/ses_email_templates.go
new file mode 100644
index 0000000..cc4abb8
--- /dev/null
+++ b/aws/resources/ses_email_templates.go
@@ -0,0 +1,73 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Returns a formatted string of email template names
+func (sem *SesEmailTemplates) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+	param := &ses.ListTemplatesInput{}
+
+	result, err := sem.Client.ListTemplates(param)
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var templates []*string
+	for _, template := range result.TemplatesMetadata {
+		createdAt := template.CreatedTimestamp
+		if configObj.SESEmailTemplates.ShouldInclude(config.ResourceValue{
+			Name: template.Name,
+			Time: createdAt,
+		}) {
+			templates = append(templates, template.Name)
+		}
+	}
+
+	return templates, nil
+}
+
+// Deletes all templates
+func (sem *SesEmailTemplates) nukeAll(templates []*string) error {
+	if len(templates) == 0 {
+		logging.Debugf("No SES email templates to nuke in region %s", sem.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting all SES email templates in region %s", sem.Region)
+	var deletedIds []*string
+
+	for _, template := range templates {
+		params := &ses.DeleteTemplateInput{
+			TemplateName: template,
+		}
+
+		_, err := sem.Client.DeleteTemplate(params)
+
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(template),
+			ResourceType: "SES email templates",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Debugf("[Failed] %s", err)
+		} else {
+			deletedIds = append(deletedIds, template)
+			logging.Debugf("Deleted SES email templates: %s", *template)
+		}
+	}
+
+	logging.Debugf("[OK] %d SES email template(s) deleted in %s", len(deletedIds), sem.Region)
+
+	return nil
+}
diff --git a/aws/resources/ses_email_templates_types.go b/aws/resources/ses_email_templates_types.go
new file mode 100644
index 0000000..8ed4d9d
--- /dev/null
+++ b/aws/resources/ses_email_templates_types.go
@@ -0,0 +1,58 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// SesEmailTemplates - represents all ses email templates
+type SesEmailTemplates struct {
+	BaseAwsResource
+	Client sesiface.SESAPI
+	Region string
+	Ids    []string
+}
+
+func (set *SesEmailTemplates) Init(session *session.Session) {
+	set.Client = ses.New(session)
+}
+
+// ResourceName - the simple name of the aws resource
+func (set *SesEmailTemplates) ResourceName() string {
+	return "ses-email-template"
+}
+
+// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle
+func (set *SesEmailTemplates) MaxBatchSize() int {
+	return maxBatchSize
+}
+
+// ResourceIdentifiers - The names of the ses email templates
+func (set *SesEmailTemplates) ResourceIdentifiers() []string {
+	return set.Ids
+}
+
+func (set *SesEmailTemplates) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := set.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	set.Ids = awsgo.StringValueSlice(identifiers)
+	return set.Ids, nil
+}
+
+// Nuke - nuke 'em all!!!
+func (set *SesEmailTemplates) Nuke(identifiers []string) error {
+	if err := set.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/ses_identity.go b/aws/resources/ses_identity.go
new file mode 100644
index 0000000..ab45d04
--- /dev/null
+++ b/aws/resources/ses_identity.go
@@ -0,0 +1,68 @@
+package resources
+
+import (
+	"context"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/cloud-nuke/logging"
+	"github.com/gruntwork-io/cloud-nuke/report"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// Returns a formatted string of ses-identities IDs
+func (sid *SesIdentities) getAll(c context.Context, configObj config.Config) ([]*string, error) {
+
+	param := &ses.ListIdentitiesInput{}
+
+	result, err := sid.Client.ListIdentities(param)
+	if err != nil {
+		return nil, errors.WithStackTrace(err)
+	}
+
+	var ids []*string
+	for _, id := range result.Identities {
+		if configObj.SESIdentity.ShouldInclude(config.ResourceValue{Name: id}) {
+			ids = append(ids, id)
+		}
+	}
+
+	return ids, nil
+}
+
+// Deletes all identities
+func (sid *SesIdentities) nukeAll(ids []*string) error {
+	if len(ids) == 0 {
+		logging.Debugf("No SES identities to nuke in region %s", sid.Region)
+		return nil
+	}
+
+	logging.Debugf("Deleting all SES identities in region %s", sid.Region)
+	var deletedIds []*string
+
+	for _, id := range ids {
+		params := &ses.DeleteIdentityInput{
+			Identity: id,
+		}
+		_, err := sid.Client.DeleteIdentity(params)
+		// Record status of this resource
+		e := report.Entry{
+			Identifier:   aws.StringValue(id),
+			ResourceType: "SES identity",
+			Error:        err,
+		}
+		report.Record(e)
+
+		if err != nil {
+			logging.Debugf("[Failed] %s", err)
+		} else {
+			deletedIds = append(deletedIds, id)
+			logging.Debugf("Deleted SES identity: %s", *id)
+		}
+	}
+
+	logging.Debugf("[OK] %d SES identity(s) deleted in %s", len(deletedIds), sid.Region)
+
+	return nil
+}
diff --git a/aws/resources/ses_identity_types.go b/aws/resources/ses_identity_types.go
new file mode 100644
index 0000000..c7bc0df
--- /dev/null
+++ b/aws/resources/ses_identity_types.go
@@ -0,0 +1,54 @@
+package resources
+
+import (
+	"context"
+
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/gruntwork-io/go-commons/errors"
+)
+
+// SesIdentities - represents all SES identities
+type SesIdentities struct {
+	BaseAwsResource
+	Client sesiface.SESAPI
+	Region string
+	Ids    []string
+}
+
+func (Sid *SesIdentities) Init(session *session.Session) {
+	Sid.Client = ses.New(session)
+}
+
+func (Sid *SesIdentities) ResourceName() string {
+	return "ses-identity"
+}
+
+func (Sid *SesIdentities) MaxBatchSize() int {
+	return maxBatchSize
+}
+
+func (Sid *SesIdentities) ResourceIdentifiers() []string {
+	return Sid.Ids
+}
+
+func (Sid *SesIdentities) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
+	identifiers, err := Sid.getAll(c, configObj)
+	if err != nil {
+		return nil, err
+	}
+
+	Sid.Ids = awsgo.StringValueSlice(identifiers)
+	return Sid.Ids, nil
+}
+
+func (Sid *SesIdentities) Nuke(identifiers []string) error {
+	if err := Sid.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	return nil
+}
diff --git a/aws/resources/transit_gateway.go b/aws/resources/transit_gateway.go
index 336ad03..b610f10 100644
--- a/aws/resources/transit_gateway.go
+++ b/aws/resources/transit_gateway.go
@@ -2,12 +2,12 @@ package resources
 
 import (
 	"context"
-	cerrors "errors"
 	"time"
 
 	"github.com/aws/aws-sdk-go/aws"
 	awsgo "github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ec2"
+	goerror "github.com/go-errors/errors"
 	"github.com/gruntwork-io/cloud-nuke/config"
 	"github.com/gruntwork-io/cloud-nuke/logging"
 	"github.com/gruntwork-io/cloud-nuke/report"
@@ -17,25 +17,11 @@ import (
 	commonTelemetry "github.com/gruntwork-io/go-commons/telemetry"
 )
 
-/*
-NOTE on the Apporach used:-Using the `dry run` approach on verifying the nuking permission in case of a scoped IAM role.
-IAM:simulateCustomPolicy : could also be used but the IAM role itself needs permission for simulateCustomPolicy method
-else this would not get the desired result. Also in case of multiple t-gateway, if only some has permssion to be nuked,
-the t-gateway resource ids needs to be passed individually inside the IAM:simulateCustomPolicy to get the desired result,
-else all would result in `Implicit-deny` as response- this might increase the time complexity.Using dry run to avoid this.
-*/
-func (tgw *TransitGateways) VerifyNukablePermissions(ctx context.Context, ids []*string) {
-	// check permissions without actually performing the nuke operation
-	for _, id := range ids {
-		// dry run set as true , checks permission without actualy making the request
-		params := &ec2.DeleteTransitGatewayInput{
-			TransitGatewayId: id,
-			DryRun:           aws.Bool(true),
-		}
-		_, err := tgw.Client.DeleteTransitGateway(params)
-		tgw.Nukable[*id] = !util.IsAwsUnauthorizedError(err)
-	}
-}
+// [Note 1] :  NOTE on the Apporach used:-Using the `dry run` approach on verifying the nuking permission in case of a scoped IAM role.
+// IAM:simulateCustomPolicy : could also be used but the IAM role itself needs permission for simulateCustomPolicy method
+//else this would not get the desired result. Also in case of multiple t-gateway, if only some has permssion to be nuked,
+// the t-gateway resource ids needs to be passed individually inside the IAM:simulateCustomPolicy to get the desired result,
+// else all would result in `Implicit-deny` as response- this might increase the time complexity.Using dry run to avoid this.
 
 // Returns a formatted string of TransitGateway IDs
 func (tgw *TransitGateways) getAll(c context.Context, configObj config.Config) ([]*string, error) {
@@ -46,16 +32,32 @@ func (tgw *TransitGateways) getAll(c context.Context, configObj config.Config) (
 		return nil, errors.WithStackTrace(err)
 	}
 
+	currentOwner := c.Value(util.AccountIdKey)
 	var ids []*string
 	for _, transitGateway := range result.TransitGateways {
 		if configObj.TransitGateway.ShouldInclude(config.ResourceValue{Time: transitGateway.CreationTime}) &&
 			awsgo.StringValue(transitGateway.State) != "deleted" && awsgo.StringValue(transitGateway.State) != "deleting" {
 			ids = append(ids, transitGateway.TransitGatewayId)
 		}
+
+		if currentOwner != nil && transitGateway.OwnerId != nil && currentOwner != awsgo.StringValue(transitGateway.OwnerId) {
+			tgw.SetNukableStatus(*transitGateway.TransitGatewayId, util.ErrDifferentOwner)
+			continue
+		}
 	}
 
 	// Check and verfiy the list of allowed nuke actions
-	tgw.VerifyNukablePermissions(c, ids)
+	// VerifyNukablePermissions is used to iterate over a list of Transit Gateway IDs (ids) and execute a provided function (func(id *string) error).
+	// The function, attempts to delete a Transit Gateway with the specified ID in a dry-run mode (checking permissions without actually performing the delete operation). The result of this operation (error or success) is then captured.
+	// See more at [Note 1]
+	tgw.VerifyNukablePermissions(ids, func(id *string) error {
+		params := &ec2.DeleteTransitGatewayInput{
+			TransitGatewayId: id,
+			DryRun:           aws.Bool(true), // dry run set as true , checks permission without actualy making the request
+		}
+		_, err := tgw.Client.DeleteTransitGateway(params)
+		return err
+	})
 
 	return ids, nil
 }
@@ -73,9 +75,9 @@ func (tgw *TransitGateways) nukeAll(ids []*string) error {
 
 	for _, id := range ids {
 		//check the id has the permission to nuke, if not. continue the execution
-		if nukable, err := tgw.IsNukable(*id); !nukable && err == nil {
+		if nukable, err := tgw.IsNukable(*id); !nukable {
 			//not adding the report on final result hence not adding a record entry here
-			logging.Debugf("[Skipping] %s nuke while you didn't have the permission", *id)
+			logging.Debugf("[Skipping] %s nuke because %v", *id, err)
 			continue
 		}
 
@@ -219,7 +221,7 @@ func (tgw *TransitGatewaysVpcAttachment) nukeAll(ids []*string) error {
 		}
 	}
 
-	if waiterr := waitForTransitGatewayAttachmentToBeDeleted(*tgw); waiterr != nil {
+	if waiterr := waitForTransitGatewayAttachementToBeDeleted(*tgw); waiterr != nil {
 		return errors.WithStackTrace(waiterr)
 	}
 	logging.Debugf(("[OK] %d Transit Gateway Vpc Attachment(s) deleted in %s"), len(deletedIds), tgw.Region)
@@ -267,7 +269,7 @@ func (tgpa *TransitGatewayPeeringAttachment) nukeAll(ids []*string) error {
 	return nil
 }
 
-func waitForTransitGatewayAttachmentToBeDeleted(tgw TransitGatewaysVpcAttachment) error {
+func waitForTransitGatewayAttachementToBeDeleted(tgw TransitGatewaysVpcAttachment) error {
 	for i := 0; i < 30; i++ {
 		gateways, err := tgw.Client.DescribeTransitGatewayVpcAttachments(
 			&ec2.DescribeTransitGatewayVpcAttachmentsInput{
@@ -290,5 +292,5 @@ func waitForTransitGatewayAttachmentToBeDeleted(tgw TransitGatewaysVpcAttachment
 		time.Sleep(10 * time.Second)
 	}
 
-	return cerrors.New("timed out waiting for transit gateway attachments to be successfully deleted")
+	return goerror.New("timed out waiting for transit gateway attahcments to be successfully deleted")
 }
diff --git a/aws/resources/transit_gateway_types.go b/aws/resources/transit_gateway_types.go
index d08503f..7954db3 100644
--- a/aws/resources/transit_gateway_types.go
+++ b/aws/resources/transit_gateway_types.go
@@ -151,13 +151,14 @@ type TransitGateways struct {
 	Client ec2iface.EC2API
 	Region string
 	Ids    []string
-	// A key-value of identifiers and nukable status
-	Nukable map[string]bool
 }
 
 func (tgw *TransitGateways) Init(session *session.Session) {
+	// to initialize base resource
+	// NOTE : This is madatory to initialize the nukables map
+	tgw.BaseAwsResource.Init(session)
 	tgw.Client = ec2.New(session)
-	tgw.Nukable = map[string]bool{}
+
 }
 
 // ResourceName - the simple name of the aws resource
@@ -193,12 +194,3 @@ func (tgw *TransitGateways) Nuke(identifiers []string) error {
 
 	return nil
 }
-
-// IsNukable - Checks whether the given identifier is authorized to nuke
-func (tgw *TransitGateways) IsNukable(identifier string) (bool, error) {
-	if status, ok := tgw.Nukable[identifier]; ok && status {
-		return true, nil
-	}
-
-	return false, nil
-}
diff --git a/config/config.go b/config/config.go
index 2fc9e6f..99def08 100644
--- a/config/config.go
+++ b/config/config.go
@@ -74,6 +74,11 @@ type Config struct {
 	Redshift                        ResourceType               `yaml:"Redshift"`
 	RdsSnapshot                     ResourceType               `yaml:"RdsSnapshot"`
 	S3                              ResourceType               `yaml:"s3"`
+	SESIdentity                     ResourceType               `yaml:"SesIdentity"`
+	SESConfigurationSet             ResourceType               `yaml:"SesConfigurationset"`
+	SESReceiptRuleSet               ResourceType               `yaml:"SesReceiptRuleSet"`
+	SESReceiptFilter                ResourceType               `yaml:"SesReceiptFilter"`
+	SESEmailTemplates               ResourceType               `yaml:"SesEmailTemplates"`
 	SNS                             ResourceType               `yaml:"SNS"`
 	SQS                             ResourceType               `yaml:"SQS"`
 	SageMakerNotebook               ResourceType               `yaml:"SageMakerNotebook"`
diff --git a/ui/ui.go b/ui/ui.go
index b2c3eb9..b1db2a3 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -138,10 +138,12 @@ func RenderResourcesAsTable(account *aws.AwsAccountResources) error {
 	for region, resourcesInRegion := range account.Resources {
 		for _, foundResources := range resourcesInRegion.Resources {
 			for _, identifier := range (*foundResources).ResourceIdentifiers() {
-				isnukable := "-"
-				if marked, err := (*foundResources).IsNukable(identifier); err == nil {
-					isnukable = fmt.Sprintf("%v", marked)
+				isnukable := SuccessEmoji
+				_, err := (*foundResources).IsNukable(identifier)
+				if err != nil {
+					isnukable = err.Error()
 				}
+
 				tableData = append(tableData, []string{(*foundResources).ResourceName(), region, identifier, isnukable})
 			}
 		}
diff --git a/util/error.go b/util/error.go
index 3be79fb..f9791c5 100644
--- a/util/error.go
+++ b/util/error.go
@@ -1,17 +1,23 @@
 package util
 
 import (
+	"errors"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 )
 
+var ErrInSufficientPermission = errors.New("error:INSUFFICIENT_PERMISSION")
+var ErrDifferentOwner = errors.New("error:DIFFERENT_OWNER")
+
 const AWsUnauthorizedError string = "UnauthorizedOperation"
 
-// IsAwsUnauthorizedError : checks whether the aws returned error is AWsUnauthorizedError
-// For any unauthorised error we can use the same code as AWS returns the same code in this scenario
+// TransformAWSError
+// this function is used to handle AWS errors and mapping them to a custom error message
+// This could be part of a larger error-handling strategy that interacts with AWS services,
+// providing a more human-readable error message for certain conditions
 // ref : https://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html
-func IsAwsUnauthorizedError(err error) bool {
+func TransformAWSError(err error) error {
 	if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == AWsUnauthorizedError {
-		return true
+		return ErrInSufficientPermission
 	}
-	return false
+	return nil
 }


Test patch:
diff --git a/aws/resources/ses_configuration_set_test.go b/aws/resources/ses_configuration_set_test.go
new file mode 100644
index 0000000..bbac1a0
--- /dev/null
+++ b/aws/resources/ses_configuration_set_test.go
@@ -0,0 +1,93 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedSesConfigurationSet struct {
+	sesiface.SESAPI
+	DeleteConfigurationSetOutput ses.DeleteConfigurationSetOutput
+	ListConfigurationSetsOutput  ses.ListConfigurationSetsOutput
+}
+
+func (m mockedSesConfigurationSet) ListConfigurationSets(input *ses.ListConfigurationSetsInput) (*ses.ListConfigurationSetsOutput, error) {
+	return &m.ListConfigurationSetsOutput, nil
+}
+
+func (m mockedSesConfigurationSet) DeleteConfigurationSet(*ses.DeleteConfigurationSetInput) (*ses.DeleteConfigurationSetOutput, error) {
+	return &m.DeleteConfigurationSetOutput, nil
+}
+
+var (
+	id1                = "test-id-1"
+	id2                = "test-id-2"
+	configurationsSet1 = ses.ConfigurationSet{
+		Name: awsgo.String(id1),
+	}
+	configurationsSet2 = ses.ConfigurationSet{
+		Name: awsgo.String(id2),
+	}
+)
+
+func TestSesConfigurationSet_GetAll(t *testing.T) {
+	t.Parallel()
+
+	identity := SesConfigurationSet{
+		Client: mockedSesConfigurationSet{
+			ListConfigurationSetsOutput: ses.ListConfigurationSetsOutput{
+				ConfigurationSets: []*ses.ConfigurationSet{
+					&configurationsSet1,
+					&configurationsSet2,
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{id1, id2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(id2),
+					}}},
+			},
+			expected: []string{id1},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := identity.getAll(context.Background(), config.Config{
+				SESConfigurationSet: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, awsgo.StringValueSlice(names))
+		})
+	}
+}
+
+func TestSesConfigurationSet_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	identity := SesConfigurationSet{
+		Client: mockedSesConfigurationSet{},
+	}
+
+	err := identity.nukeAll([]*string{aws.String("test")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/ses_email_receiving_test.go b/aws/resources/ses_email_receiving_test.go
new file mode 100644
index 0000000..e602cae
--- /dev/null
+++ b/aws/resources/ses_email_receiving_test.go
@@ -0,0 +1,178 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedSesReceiptRule struct {
+	sesiface.SESAPI
+	DeleteReceiptRuleSetOutput ses.DeleteReceiptRuleSetOutput
+	ListReceiptRuleSetsOutput  ses.ListReceiptRuleSetsOutput
+}
+
+func (m mockedSesReceiptRule) ListReceiptRuleSets(input *ses.ListReceiptRuleSetsInput) (*ses.ListReceiptRuleSetsOutput, error) {
+	return &m.ListReceiptRuleSetsOutput, nil
+}
+
+func (m mockedSesReceiptRule) DeleteReceiptRuleSet(*ses.DeleteReceiptRuleSetInput) (*ses.DeleteReceiptRuleSetOutput, error) {
+	return &m.DeleteReceiptRuleSetOutput, nil
+}
+
+func TestSesReceiptRule_GetAll(t *testing.T) {
+
+	id1 := "test-id-1"
+	id2 := "test-id-2"
+	metadata1 := ses.ReceiptRuleSetMetadata{
+		CreatedTimestamp: aws.Time(time.Now()),
+		Name:             aws.String(id1),
+	}
+	metadata2 := ses.ReceiptRuleSetMetadata{
+		CreatedTimestamp: aws.Time(time.Now().AddDate(-1, 0, 0)),
+		Name:             aws.String(id2),
+	}
+	t.Parallel()
+
+	sesRule := SesReceiptRule{
+		Client: mockedSesReceiptRule{
+			ListReceiptRuleSetsOutput: ses.ListReceiptRuleSetsOutput{
+				RuleSets: []*ses.ReceiptRuleSetMetadata{
+					&metadata1,
+					&metadata2,
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{id1, id2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(id2),
+					}}},
+			},
+			expected: []string{id1},
+		},
+		"timeAfterExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					TimeAfter: aws.Time(time.Now().Add(-1 * time.Hour)),
+				}},
+			expected: []string{id2},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := sesRule.getAll(context.Background(), config.Config{
+				SESReceiptRuleSet: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, awsgo.StringValueSlice(names))
+		})
+	}
+}
+
+func TestSesReceiptRule_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	sesRule := SesReceiptRule{
+		Client: mockedSesReceiptRule{},
+	}
+
+	err := sesRule.nukeAll([]*string{aws.String("test")})
+	require.NoError(t, err)
+}
+
+// //////////////receipt Ip filters///////////////////////////
+
+type mockedSesReceiptFilter struct {
+	sesiface.SESAPI
+	DeleteReceiptFilterOutput ses.DeleteReceiptFilterOutput
+	ListReceiptFiltersOutput  ses.ListReceiptFiltersOutput
+}
+
+func (m mockedSesReceiptFilter) ListReceiptFilters(input *ses.ListReceiptFiltersInput) (*ses.ListReceiptFiltersOutput, error) {
+	return &m.ListReceiptFiltersOutput, nil
+}
+
+func (m mockedSesReceiptFilter) DeleteReceiptFilter(*ses.DeleteReceiptFilterInput) (*ses.DeleteReceiptFilterOutput, error) {
+	return &m.DeleteReceiptFilterOutput, nil
+}
+
+func TestSesReceiptFilter_GetAll(t *testing.T) {
+
+	id1 := "test-id-1"
+	id2 := "test-id-2"
+	metadata1 := ses.ReceiptFilter{
+		Name: aws.String(id1),
+	}
+	metadata2 := ses.ReceiptFilter{
+		Name: aws.String(id2),
+	}
+	t.Parallel()
+
+	sesRule := SesReceiptFilter{
+		Client: mockedSesReceiptFilter{
+			ListReceiptFiltersOutput: ses.ListReceiptFiltersOutput{
+				Filters: []*ses.ReceiptFilter{
+					&metadata1, &metadata2,
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{id1, id2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(id2),
+					}}},
+			},
+			expected: []string{id1},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := sesRule.getAll(context.Background(), config.Config{
+				SESReceiptFilter: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, awsgo.StringValueSlice(names))
+		})
+	}
+}
+func TestSesReceiptFilter_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	sesRule := SesReceiptFilter{
+		Client: mockedSesReceiptFilter{},
+	}
+
+	err := sesRule.nukeAll([]*string{aws.String("test")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/ses_email_templates_test.go b/aws/resources/ses_email_templates_test.go
new file mode 100644
index 0000000..451080e
--- /dev/null
+++ b/aws/resources/ses_email_templates_test.go
@@ -0,0 +1,101 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+	"time"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedSesEmailTemplates struct {
+	sesiface.SESAPI
+	DeleteTemplateOutput ses.DeleteTemplateOutput
+	ListTemplatesOutput  ses.ListTemplatesOutput
+}
+
+func (m mockedSesEmailTemplates) ListTemplates(input *ses.ListTemplatesInput) (*ses.ListTemplatesOutput, error) {
+	return &m.ListTemplatesOutput, nil
+}
+
+func (m mockedSesEmailTemplates) DeleteTemplate(*ses.DeleteTemplateInput) (*ses.DeleteTemplateOutput, error) {
+	return &m.DeleteTemplateOutput, nil
+}
+
+func TestSesEmailTemplates_GetAll(t *testing.T) {
+
+	id1 := "test-id-1"
+	id2 := "test-id-2"
+	templateMetadata1 := ses.TemplateMetadata{
+		CreatedTimestamp: aws.Time(time.Now()),
+		Name:             aws.String(id1),
+	}
+	templateMetadata2 := ses.TemplateMetadata{
+		CreatedTimestamp: aws.Time(time.Now().AddDate(-1, 0, 0)),
+		Name:             aws.String(id2),
+	}
+	t.Parallel()
+
+	sesEmailTemp := SesEmailTemplates{
+		Client: mockedSesEmailTemplates{
+			ListTemplatesOutput: ses.ListTemplatesOutput{
+				TemplatesMetadata: []*ses.TemplateMetadata{
+					&templateMetadata1,
+					&templateMetadata2,
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{id1, id2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(id2),
+					}}},
+			},
+			expected: []string{id1},
+		},
+		"timeAfterExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					TimeAfter: aws.Time(time.Now().Add(-1 * time.Hour)),
+				}},
+			expected: []string{id2},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := sesEmailTemp.getAll(context.Background(), config.Config{
+				SESEmailTemplates: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, awsgo.StringValueSlice(names))
+		})
+	}
+}
+
+func TestSesEmailTemplates_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	sesEmailTemp := SesEmailTemplates{
+		Client: mockedSesEmailTemplates{},
+	}
+
+	err := sesEmailTemp.nukeAll([]*string{aws.String("test")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/ses_identity_test.go b/aws/resources/ses_identity_test.go
new file mode 100644
index 0000000..8e9d6c6
--- /dev/null
+++ b/aws/resources/ses_identity_test.go
@@ -0,0 +1,86 @@
+package resources
+
+import (
+	"context"
+	"regexp"
+	"testing"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	awsgo "github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/ses"
+	"github.com/aws/aws-sdk-go/service/ses/sesiface"
+	"github.com/gruntwork-io/cloud-nuke/config"
+	"github.com/stretchr/testify/require"
+)
+
+type mockedSesIdentities struct {
+	sesiface.SESAPI
+	DeleteIdentityOutput ses.DeleteIdentityOutput
+	ListIdentitiesOutput ses.ListIdentitiesOutput
+}
+
+func (m mockedSesIdentities) ListIdentities(input *ses.ListIdentitiesInput) (*ses.ListIdentitiesOutput, error) {
+	return &m.ListIdentitiesOutput, nil
+}
+
+func (m mockedSesIdentities) DeleteIdentity(*ses.DeleteIdentityInput) (*ses.DeleteIdentityOutput, error) {
+	return &m.DeleteIdentityOutput, nil
+}
+
+func TestSesIdentities_GetAll(t *testing.T) {
+	t.Parallel()
+
+	id1 := "test-id-1"
+	id2 := "test-id-2"
+	identity := SesIdentities{
+		Client: mockedSesIdentities{
+			ListIdentitiesOutput: ses.ListIdentitiesOutput{
+				Identities: []*string{
+					awsgo.String(id1),
+					awsgo.String(id2),
+				},
+			},
+		},
+	}
+
+	tests := map[string]struct {
+		configObj config.ResourceType
+		expected  []string
+	}{
+		"emptyFilter": {
+			configObj: config.ResourceType{},
+			expected:  []string{id1, id2},
+		},
+		"nameExclusionFilter": {
+			configObj: config.ResourceType{
+				ExcludeRule: config.FilterRule{
+					NamesRegExp: []config.Expression{{
+						RE: *regexp.MustCompile(id2),
+					}}},
+			},
+			expected: []string{id1},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			names, err := identity.getAll(context.Background(), config.Config{
+				SESIdentity: tc.configObj,
+			})
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, awsgo.StringValueSlice(names))
+		})
+	}
+}
+
+func TestSesIdentities_NukeAll(t *testing.T) {
+	t.Parallel()
+
+	identity := SesIdentities{
+		Client: mockedSesIdentities{
+			DeleteIdentityOutput: ses.DeleteIdentityOutput{},
+		},
+	}
+
+	err := identity.nukeAll([]*string{aws.String("test")})
+	require.NoError(t, err)
+}
diff --git a/aws/resources/transit_gateway_test.go b/aws/resources/transit_gateway_test.go
index 7760216..0be916a 100644
--- a/aws/resources/transit_gateway_test.go
+++ b/aws/resources/transit_gateway_test.go
@@ -87,7 +87,6 @@ func TestTransitGateways_GetAll(t *testing.T) {
 	gatewayId1 := "gateway1"
 	gatewayId2 := "gateway2"
 	tgw := TransitGateways{
-		Nukable: make(map[string]bool),
 		Client: mockedTransitGateway{
 			DescribeTransitGatewaysOutput: ec2.DescribeTransitGatewaysOutput{
 				TransitGateways: []*ec2.TransitGateway{
@@ -105,6 +104,8 @@ func TestTransitGateways_GetAll(t *testing.T) {
 			},
 		},
 	}
+	tgw.BaseAwsResource.Init(nil)
+
 	tests := map[string]struct {
 		configObj config.ResourceType
 		expected  []string
diff --git a/config/config_test.go b/config/config_test.go
index ce37388..3aeb81f 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -83,6 +83,11 @@ func emptyConfig() *Config {
 		ResourceType{FilterRule{}, FilterRule{}},
 		ResourceType{FilterRule{}, FilterRule{}},
 		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
+		ResourceType{FilterRule{}, FilterRule{}},
 	}
 }