In our interactions with customers via a CRM, we often found ourselves deviating between CRM chats and direct emails. This deviation scattered information, with conversations about a single lead dispersed over multiple platforms, leading to reporting inconsistencies.
Our initial solution was straightforward – connect the inbox directly to the CRM. This would ensure all incoming emails would be reflected in the CRM. However, we quickly realized the flaw: not everyone wants their entire inbox mirrored in the CRM.
A seemingly unrelated event provided the spark for our solution. A comment notification from a Jira ticket mentioned that one could reply directly to the email to comment on the ticket. The email address was a unique identifier, something like <uuid >@jira-support.com . This got us thinking, and our research found Slack employing a similar mechanism.
Procedures
10 inbound-smtp.<regionInboundUrl>.amazonaws.com
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowSESPuts", "Effect": "Allow", "Principal": { "Service": "ses.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::<bucketName>/*", "Condition": { "StringEquals": { "aws:Referer": "<awsAccountId>" } } } ] }
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "s3:GetObject", "ses:SendRawEmail" ], "Resource": [ "arn:aws:s3:::<bucketName>/*", "arn:aws:ses:<region>:<awsAccountId>:identity/*" ] } ] }
<bucketName>
 with the name of the S3 bucket that you created earlier.<region>
 with the name of the AWS Region that you created the bucket in.<awsAccountId>
 with your AWS account ID. For more information, see Create a Customer Managed Policy in the IAM User Guide.# Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # This file is licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. A copy of the # License is located at # # http://aws.amazon.com/apache2.0/ # # This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import os import boto3 import email import re from botocore.exceptions import ClientError from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication region = os.environ['Region'] def get_message_from_s3(message_id): incoming_email_bucket = os.environ['MailS3Bucket'] incoming_email_prefix = os.environ['MailS3Prefix'] if incoming_email_prefix: object_path = (incoming_email_prefix + "/" + message_id) else: object_path = message_id object_http_path = (f"http://s3.console.aws.amazon.com/s3/object/{incoming_email_bucket}/{object_path}?region={region}") # Create a new S3 client. client_s3 = boto3.client("s3") # Get the email object from the S3 bucket. object_s3 = client_s3.get_object(Bucket=incoming_email_bucket, Key=object_path) # Read the content of the message. file = object_s3['Body'].read() file_dict = { "file": file, "path": object_http_path } return file_dict def create_message(file_dict): sender = os.environ['MailSender'] recipient = os.environ['MailRecipient'] separator = ";" # Parse the email body. mailobject = email.message_from_string(file_dict['file'].decode('utf-8')) # Create a new subject line. subject_original = mailobject['Subject'] subject = "FW: " + subject_original # The body text of the email. body_text = ("The attached message was received from " + separator.join(mailobject.get_all('From')) + ". This message is archived at " + file_dict['path']) # The file name to use for the attached message. Uses regex to remove all # non-alphanumeric characters, and appends a file extension. filename = re.sub('[^0-9a-zA-Z]+', '_', subject_original) + ".eml" # Create a MIME container. msg = MIMEMultipart() # Create a MIME text part. text_part = MIMEText(body_text, _subtype="html") # Attach the text part to the MIME message. msg.attach(text_part) # Add subject, from and to lines. msg['Subject'] = subject msg['From'] = sender msg['To'] = recipient # Create a new MIME object. att = MIMEApplication(file_dict["file"], filename) att.add_header("Content-Disposition", 'attachment', filename=filename) # Attach the file object to the message. msg.attach(att) message = { "Source": sender, "Destinations": recipient, "Data": msg.as_string() } return message def send_email(message): aws_region = os.environ['Region'] # Create a new SES client. client_ses = boto3.client('ses', region) # Send the email. try: #Provide the contents of the email. response = client_ses.send_raw_email( Source=message['Source'], Destinations=[ message['Destinations'] ], RawMessage={ 'Data':message['Data'] } ) # Display an error if something goes wrong. except ClientError as e: output = e.response['Error']['Message'] else: output = "Email sent! Message ID: " + response['MessageId'] return output def lambda_handler(event, context): # Get the unique ID of the message. This corresponds to the name of the file # in S3. message_id = event['Records'][0]['ses']['mail']['messageId'] print(f"Received message ID {message_id}") # Retrieve the file from the S3 bucket. file_dict = get_message_from_s3(message_id) # Create the message. message = create_message(file_dict) # Send the email and print the result. result = send_email(message) print(result)
You can optionally create an Amazon SNS topic. This step is helpful for troubleshooting purposes, or if you just want to receive additional notifications when you receive a message.
For more information, see Setting Up a Receipt Rule in the Amazon SES Developer Guide.
MailRecipient
 variable of the Lambda function.If you send a test message, but it is never forwarded to your destination email address, do the following:
If you send a test email to your receiving domain, but you receive a bounce notification, do the following:
The cost of implementing this solution is minimal. If you receive 10,000 emails per month, and each email is 2KB in size, you pay $1.00 for your use of Amazon SES. For more information, see Amazon SES Pricing.
You also pay a small charge to store incoming emails in Amazon S3. The charge for storing 1,000 emails that are each 2KB in size is less than one cent. Your use of Amazon S3 might qualify for the AWS Free Usage Tier. For more information, see Amazon S3 Pricing.
Finally, you pay for your use of AWS Lambda. With Lambda, you pay for the number of requests you make, for the amount of compute time that you use, and for the amount of memory that you use. If you use Lambda to forward 1,000 emails that are each 2KB in size, you pay no more than a few cents. Your use of AWS Lambda might qualify for the AWS Free Usage Tier. For more information, see AWS Lambda Pricing.
Note: These cost estimates don’t include the costs associated with purchasing a domain, since many users already have their own domains. The cost of obtaining a domain is the most expensive part of implementing this solution.
This solution makes it possible to forward incoming email from one of your Amazon SES verified domains to an email address that isn’t necessarily verified. It’s also useful if you have multiple AWS accounts, and you want incoming messages to be sent from each of those accounts to a single destination. We hope you’ve found this to be helpful!
Let's collaborate to turn your business challenges into AI-powered success stories.
Get Started