<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krzysztof Królikowski</title>
    <description>The latest articles on DEV Community by Krzysztof Królikowski (@kkrolikowski).</description>
    <link>https://dev.to/kkrolikowski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F100775%2F8850caf9-6139-4f91-9c48-b38d34f405b8.png</url>
      <title>DEV Community: Krzysztof Królikowski</title>
      <link>https://dev.to/kkrolikowski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kkrolikowski"/>
    <language>en</language>
    <item>
      <title>Setting Up and Handling Email Aliases in AWS SES</title>
      <dc:creator>Krzysztof Królikowski</dc:creator>
      <pubDate>Tue, 24 Dec 2024 06:49:29 +0000</pubDate>
      <link>https://dev.to/kkrolikowski/setting-up-and-handling-email-aliases-in-aws-ses-3m5h</link>
      <guid>https://dev.to/kkrolikowski/setting-up-and-handling-email-aliases-in-aws-ses-3m5h</guid>
      <description>&lt;p&gt;A while ago, I started looking for a service that would allow me to implement email alias functionality. Until now, I have had it configured on Postfix running on my VPS server. Setting up and maintaining an email service is time-consuming. Tasks such as updating SSL certificates, spam filtering, and antivirus protection require ongoing attention. A poorly configured mail server can easily become a target for bots sending spam and malware.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Simple Email Service
&lt;/h2&gt;

&lt;p&gt;AWS SES is a service that operates on a “pay as you go” model. It is designed for sending both transactional notifications and marketing emails. More information about the service can be found at: &lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;https://aws.amazon.com/ses/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The SES service is available in every AWS account. By default, it is configured in sandbox mode. The sandbox mode has several limitations, the most important are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The inability to receive emails from unverified senders&lt;/li&gt;
&lt;li&gt;The ability to deliver emails only to a list of verified email addresses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start using the production plan, you need to request it through the AWS web console. The request is reviewed by AWS Support, which can adjust the account settings to enable the production plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Domain verification
&lt;/h4&gt;

&lt;p&gt;The primary requirement for running the discussed solution is having a domain correctly verified in the SES service. The domain should have a Verified status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F27401cfflgukgrdxwgn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F27401cfflgukgrdxwgn4.png" alt="domain verification status" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, DKIM and DMARC records must be configured.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☝️ If the domain is hosted on Route53, this can be done using Terraform code. Otherwise, all necessary records can be obtained directly from the AWS SES console.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Verified domain identity&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ses_domain_identity"&lt;/span&gt; &lt;span class="s2"&gt;"ses_domain"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ses_domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# DKIM identity for email domain&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ses_domain_dkim"&lt;/span&gt; &lt;span class="s2"&gt;"ses_domain"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ses_domain_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ses_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"ses_domain_dkim_record"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_ses_domain_dkim.ses_domain.dkim_tokens[count.index]}._domainkey"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CNAME"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"600"&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"${aws_ses_domain_dkim.ses_domain.dkim_tokens[count.index]}.dkim.amazonses.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"ses_domain_verification_record"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"_amazonses.${var.ses_domain}"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TXT"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"600"&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_ses_domain_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ses_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verification_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"ses_dmarc_record"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"_dmarc.${var.ses_domain}"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"TXT"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"600"&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"v=DMARC1; p=none; rua=mailto:postmaster@${var.ses_domain}"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  MX Record
&lt;/h4&gt;

&lt;p&gt;To receive emails from external sources, you need to configure an MX record that points to the SMTP server address of the SES service.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route53_record"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_id&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain_name&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MX"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10 inbound-smtp.region.amazonaws.com"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The region should be replaced with the one where your SES service is running.&lt;/p&gt;
&lt;h3&gt;
  
  
  Solution architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl94fqtrg6vepsyg6t08j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl94fqtrg6vepsyg6t08j.png" alt="email aliases architecture" width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Email processing will be covered later in this article in details.&lt;/p&gt;

&lt;p&gt;The Lambda function receives an event from SES, which contains information about the email alias address and the MessageID. The MessageID is also used as the key name in the S3 bucket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;☝️ By utilizing the S3 bucket, we gain the ability to receive emails &amp;gt; up to &lt;strong&gt;40MB&lt;/strong&gt; in size. The maximum size of an email that can be sent via the boto3 API is &lt;strong&gt;10MB&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After the email is processed, it is sent via SES to the list of recipients.&lt;/p&gt;
&lt;h4&gt;
  
  
  Implementation Challenges
&lt;/h4&gt;

&lt;p&gt;The SES service comes with certain limitations. The most significant is the inability to send emails with a &lt;code&gt;FROM&lt;/code&gt; header that has not been verified in the service. This is a major obstacle, as it is often not feasible to verify the email address with each individual entity, especially since many of them are automated systems.&lt;/p&gt;
&lt;h5&gt;
  
  
  Implemented Workaround
&lt;/h5&gt;

&lt;p&gt;The issue described above was resolved by breaking down the received email into its components. The Lambda function analyzes the structure of the email, extracts all textual elements, images, and attachments, modifies the &lt;code&gt;From&lt;/code&gt; and &lt;code&gt;To&lt;/code&gt; addresses, reassembles the email, and sends it to the alias recipient list.&lt;/p&gt;
&lt;h3&gt;
  
  
  SES Email receiving
&lt;/h3&gt;

&lt;p&gt;To get the solution working, you need to configure email receiving rules. Refer to: &lt;a href="https://docs.aws.amazon.com/ses/latest/dg/receiving-email-concepts.html#receiving-email-concepts-rules" rel="noopener noreferrer"&gt;Email Receiving Concepts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the discussed solution, an email rule named email-aliases is defined, which contains two rule sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trigger-mail-forwarding&lt;/li&gt;
&lt;li&gt;reject&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04ps9021pj9nr3t8t9xr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04ps9021pj9nr3t8t9xr.png" alt="Block algorithm representing email receiving logic" width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Email Receiving Rules and Actions
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;☝️ The position parameter plays a crucial role as it determines the execution order of actions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The action to save data to S3 must be executed before the Lambda function is triggered. Otherwise, the Lambda function will not receive information about the email stored in the bucket.&lt;/p&gt;
&lt;h5&gt;
  
  
  Receiving rules examples
&lt;/h5&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ses_receipt_rule_set"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rule_set_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruleset_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Add a header to the email and store it in S3&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ses_receipt_rule"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"trigger-mail-forwarding"&lt;/span&gt;
  &lt;span class="nx"&gt;rule_set_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ses_receipt_rule_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rule_set_name&lt;/span&gt;
  &lt;span class="nx"&gt;recipients&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipients&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;scan_enabled&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;tls_policy&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Require"&lt;/span&gt;

  &lt;span class="nx"&gt;add_header_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;header_name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"X-SES-Forwarded-By"&lt;/span&gt;
    &lt;span class="nx"&gt;header_value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SES Rule ${aws_ses_receipt_rule_set.this.rule_set_name}"&lt;/span&gt;
    &lt;span class="nx"&gt;position&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;s3_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3_bucket_name&lt;/span&gt;
    &lt;span class="nx"&gt;position&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lambda_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;function_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_function_arn&lt;/span&gt;
    &lt;span class="nx"&gt;invocation_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Event"&lt;/span&gt;
    &lt;span class="nx"&gt;position&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;stop_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scope&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RuleSet"&lt;/span&gt;
    &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ses_receipt_rule"&lt;/span&gt; &lt;span class="s2"&gt;"reject"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rule_set_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ses_receipt_rule_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rule_set_name&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"reject"&lt;/span&gt;
  &lt;span class="nx"&gt;enabled&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;scan_enabled&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;tls_policy&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Require"&lt;/span&gt;

  &lt;span class="nx"&gt;bounce_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This email address is not accepted by this domain."&lt;/span&gt;
    &lt;span class="nx"&gt;sender&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noreply_email&lt;/span&gt;
    &lt;span class="nx"&gt;smtp_reply_code&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"550"&lt;/span&gt;
    &lt;span class="nx"&gt;position&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;stop_action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scope&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RuleSet"&lt;/span&gt;
    &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Lambda code
&lt;/h3&gt;

&lt;p&gt;The function code is available on my github. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kkrolikowski" rel="noopener noreferrer"&gt;
        kkrolikowski
      &lt;/a&gt; / &lt;a href="https://github.com/kkrolikowski/ses-alias-lambda" rel="noopener noreferrer"&gt;
        ses-alias-lambda
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Lambda designed to send raw emails through AWS Simple Email Services
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AWS SES Email Alias Lambda&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;This function helps solve a limitation of AWS Simple Email Service: you can’t send emails to external recipients using a FROM address that hasn’t been verified. The function disassembles the email and replaces the &lt;code&gt;FROM&lt;/code&gt; address with an address from a domain verified in the SES service. It then reassembles all the parts and sends the email to the target recipients.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;The Lambda function is invoked by the SES service and receives an event containing information about the sender and the MessageID. The MessageID is required to locate the corresponding object with the raw email message in the S3 bucket. Based on the recipient address received in the event, email alias targets are located in the AWS SSM Parameter Store service.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Environment variables&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;br&gt;
&lt;thead&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;br&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/thead&gt;
&lt;br&gt;
&lt;tbody&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;EMAIL_BUCKET&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;S3 bucket name with raw email objects&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;ALIAS_MAP_PARAM&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;SSM parameter path with email alias mappings&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/tbody&gt;
&lt;br&gt;
&lt;/table&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Input&lt;/h3&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/kkrolikowski/ses-alias-lambda" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;It‘s distributed under the MIT license.&lt;/p&gt;

&lt;p&gt;The mentioned ses-alias Lambda function does not contain complex logic. Its task is to retrieve the email content from an S3 bucket, locate the alias recipients in SSM, reassemble the email, and send it using SES.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mailparser
&lt;/h4&gt;

&lt;p&gt;The most important part of the code is the parser, which contains the logic for processing raw email files.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h4&gt;
  
  
  MIME Standard
&lt;/h4&gt;

&lt;p&gt;Understanding the parser's functionality requires familiarity with the structure of an email message. The most common emails today are those containing more or less complex HTML code with embedded images. Additionally, there are also attachments.&lt;/p&gt;

&lt;p&gt;To ensure such messages can be correctly read by client software, the MIME standard was introduced: &lt;strong&gt;Multipurpose Internet Mail Extensions&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc2046" rel="noopener noreferrer"&gt;Read more about MIME in RFC 2046&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first document on this topic was published in 1996, long before XML and JSON standards became popular and dominated the internet.&lt;/p&gt;

&lt;p&gt;To better illustrate what we are dealing with, I have prepared the following diagram of an email message in the multipart MIME format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feb3esjgljj94mj4vtocd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feb3esjgljj94mj4vtocd.png" alt="MIME E-mail message schema" width="561" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, not all emails we encounter are of this type; there can also be messages containing only plain text or only HTML. However, there's nothing preventing such content from also being placed into an appropriate MIME-type object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The AWS Simple Email Service can be used to build functionality for handling email aliases.&lt;/p&gt;

&lt;h4&gt;
  
  
  Costs
&lt;/h4&gt;

&lt;p&gt;With a low email volume, it will cost very little. In terms of expenses, it is an attractive alternative to other email solutions available on the market.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmh5xta0w54hmwb8fepm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmh5xta0w54hmwb8fepm.png" alt="Screenshot with AWS costs dashboard" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;S3 bucket utilization: each object represents one processed email.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mj8s2athefk2l7grdbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mj8s2athefk2l7grdbb.png" alt="Screenshot with s3 resources utilization charts" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Limitations
&lt;/h4&gt;

&lt;p&gt;AWS Simple Email Service imposes restrictions on sending emails to external recipients from domains (or email addresses) that have not been verified within the service managed under the account. Even when using the production plan, this limitation still applies.&lt;/p&gt;

&lt;p&gt;As a result, emails must be sent with a modified "From" address, which necessitates implementing some form of compromise.&lt;/p&gt;

&lt;p&gt;Another limitation is the size of the emails supported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10MB: The maximum size of a single email that can be sent via Lambda, as limited by the SES API.&lt;/li&gt;
&lt;li&gt;40MB: The maximum size of a single email that AWS SES can save to an S3 bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are relatively generous limits for standard notification emails, but if you plan to send larger attachments or images, these limits need to be taken into account.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mail</category>
      <category>alias</category>
      <category>ses</category>
    </item>
  </channel>
</rss>
