How to send email to multiple recipients through AWS SES - amazon-web-services

Hello guys i'm trying to send email to multiple user through AWS SES using Python but whenever i'm trying to send a mail i got a error : Illegal address
This is my code:
def emailServiceForCustomerInformation(self, emailSubject, customerLicenseMessage, installation_name):
# logger = ToolsLogger.getOrCreateLogger(current_user.keyspace)
logger = ToolsLogger.getOrCreateRootLogger()
logger.info("Email service For Customer is started")
record = int(recordCount)
# print("emailRcord-",record)
# This address must be verified with Amazon SES.
SENDER = "Snehil singh<snehil#codedata.io>"
# is still in the sandbox, this address must be verified.
recipients = ["cosmoandysysmo#gmail.com","snehil#codedata.io"]
RECIPIENT = ", ".join(recipients)
# If necessary, replace us-east-1 with the AWS Region currently using for Amazon SES.
AWS_REGION = "us-east-1"
# The subject line for the email.
SUBJECT = emailSubject
BODY_TEXT = (customerLicenseMessage + ' ''For InstallationName-'+ installation_name)
# The character encoding for the email.
CHARSET = "UTF-8"
client = boto3.client('ses', region_name=AWS_REGION,
aws_access_key_id=config[os.environ['CONFIG_TYPE']].S3_ACCESS_KEY,
aws_secret_access_key=config[os.environ['CONFIG_TYPE']].S3_ACCESS_SECRET_KEY,
config=Config(signature_version='s3v4'))
is_success = True
# Try to send the email.
try:
# Provide the contents of the email.
response = client.send_email(
Destination={
'ToAddresses': [
RECIPIENT,
],
},
Message={
'Body': {
'Text': {
'Charset': CHARSET,
'Data': BODY_TEXT,
},
},
'Subject': {
'Charset': CHARSET,
'Data': SUBJECT,
},
},
Source=SENDER,
# If you are not using a configuration set, comment or delete the
# following line
# ConfigurationSetName=CONFIGURATION_SET,
)
# Display an error if something goes wrong.
except ClientError as e:
logger.exception(e)
print(e.response['Error']['Message'])
is_success = False
else:
# print("Email sent! Message ID:"),
# print(response['MessageId'])
logger.info("Email service is Completed and send to the mail")
return is_success
i have searched on internet but non of the answer helped This is another way i have tried https://www.jeffgeerling.com/blogs/jeff-geerling/sending-emails-multiple but this also not helpful please help me where i'm doing wrong where do i modify it please ping me if you have any questions related this...thanks in advance.

Looks to me like you should be passing in the 'recipient', not the RECIPENT string. Try something like this:
Destination={'ToAddresses':recipients}
It appears to be expecting an array, not a comma seperated list of strings.

In boto3 SES send_email documentation:
response = client.send_email(
Source='string',
Destination={
'ToAddresses': [
'string',
],
'CcAddresses': [
'string',
],
'BccAddresses': [
'string',
]
},
And if you read the SES SendEmail API call documentation, it tells you that the Destination object is:
BccAddresses.member.N
The BCC: field(s) of the message.
Type: Array of strings
Required: No
CcAddresses.member.N
The CC: field(s) of the message.
Type: Array of strings
Required: No
ToAddresses.member.N
The To: field(s) of the message.
Type: Array of strings
Required: No
In summary: don't join the address to construct RECIPIENT. RECIPIENT needs to be an array (a list, in Python) of strings, where each strings is one email address.

RECIPIENT must be array of strings > ['email1', 'email2']
and >>
Destination={
'ToAddresses': [
RECIPIENT,
],
},
to
Destination={
'ToAddresses': RECIPIENT
},

Credit to this answer
All you need to do is to put all the recipients as a list
email = ['xxx#gmail.com', 'yyy#yahoo.com', 'zzz#hotmail.com]
Then, you can modify the boto3 variables as below
Destination={'ToAddresses': email, .....}

Related

AWS SES SendBulkTemplatedEmailResponse for tracking email statues

I am referring to .net SDK here but I believe class level concepts are all same.
This is for sending bulk emails using templates (https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-personalized-email-api.html)
https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/SimpleEmail/TSendBulkTemplatedEmailResponse.html
SendBulkTemplatedEmailResponse response = client.SendBulkTemplatedEmailAsync(sendBulkTemplatedEmailRequest).Result
SendBulkTemplatedEmailRequest has more than one email addresses and SendBulkTemplatedEmailResponse is returned with individual status for each email as List<BulkEmailDestinationStatus> (https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/SimpleEmail/TBulkEmailDestinationStatus.html).
Each BulkEmailDestinationStatus has MessageId and Status (some predefined constants). But not having the email-address for which the status is returned (obviously there are more than one recipients so there are individual status for each recipient.)
With that said, how to figure out mapping from email-address to MessageId or vice-versa?
I am getting confused about what is the use of messageId in BulkEmailDestinationStatus where there is not any associated recipient email-address. Am I missing something very basic here?
While I didn't find any resources on this, I'll just leave what I gathered testing this feature, for anyone else that may find this question.
The order of the emails sent (ie. the order of the emails in the Destinations property) is the order of the returned messageIds.
So, using the docs json sample:
{
"Source":"Mary Major <mary.major#example.com>",
"Template":"MyTemplate",
"ConfigurationSetName": "ConfigSet",
"Destinations":[
{
"Destination":{
"ToAddresses":[
"anaya.iyengar#example.com"
]
},
"ReplacementTemplateData":"{ \"name\":\"Anaya\", \"favoriteanimal\":\"angelfish\" }"
},
{
"Destination":{
"ToAddresses":[
"liu.jie#example.com"
]
},
"ReplacementTemplateData":"{ \"name\":\"Liu\", \"favoriteanimal\":\"lion\" }"
},
{
"Destination":{
"ToAddresses":[
"shirley.rodriguez#example.com"
]
},
"ReplacementTemplateData":"{ \"name\":\"Shirley\", \"favoriteanimal\":\"shark\" }"
},
{
"Destination":{
"ToAddresses":[
"richard.roe#example.com"
]
},
"ReplacementTemplateData":"{}"
}
],
"DefaultTemplateData":"{ \"name\":\"friend\", \"favoriteanimal\":\"unknown\" }"
}
The object sent would be a
(SendBulkTemplatedEmailRequest) request with the following list:
request.Destinations[0].ToAddresses = {"anaya.iyengar#example.com"}
request.Destinations[1].ToAddresses = {"liu.jie#example.com"}
request.Destinations[2].ToAddresses = {"shirley.rodriguez#example.com"}
request.Destinations[3].ToAddresses = {"richard.roe#example.com"}
And the (SendBulkTemplatedEmailResponse) response would have this list:
response.Status[0].MessageId = "0000000000000000-11111111-2222-3333-4444-111111111111-000000"
response.Status[1].MessageId = "0000000000000000-11111111-2222-3333-4444-222222222222-000000"
response.Status[2].MessageId = "0000000000000000-11111111-2222-3333-4444-333333333333-000000"
response.Status[3].MessageId = "0000000000000000-11111111-2222-3333-4444-444444444444-000000"
Where:
the MessageId "0000000000000000-11111111-2222-3333-4444-111111111111-000000" refers to the email sent to anaya.iyengar#example.com;
the MessageId
"0000000000000000-11111111-2222-3333-4444-222222222222-000000"
refers to to the email sent to "liu.jie#example.com";
and so on.

Google PubSub does not trigger Function with each new message

I have a Google PubSub stream that has a set of addresses added to it each day. I want each of these addresses processed by a triggered Google Cloud Function. However, what I have seen is that each address is only processed once even though there is a new message added to the stream each day.
My question is, if the same value is added to a stream each day will it be processed as a new message? Or will it be treated as a duplicate message?
This is the scenario I am seeing. Each day the locations Cloud Function is triggered and publishes each location to the locations topic. Most of the time these are the same messages as the previous day. The only time they change is if a location closes or there is a new one added. However, what I see is that many of the locations messages are never picked up by the location_metrics Cloud Function.
The flow of the functions is like this:
Topic is triggered each day at 2a (called locations_trigger) > triggers locations Cloud Function > sends to locations topic > triggers location_metrics Cloud Function > sends to location_metrics topic
For the locations Cloud Function, it is triggered and returns all addresses correctly then sends them to the locations topic. I won't put the whole function here because there are no problems with it. For each location it retrieves there is a "publish successful" message in the log. Here is the portion that sends the location details to the topic.
project_id = "project_id"
topic_name = "locations"
topic_id = "projects/project_id/topics/locations"
publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path(project_id, topic_name)
try:
publisher.publish(topic_path, data=location_details.encode('utf-8'))
print("publish successful: ", location)
except Exception as exc:
print(exc)
An example location payload that is sent is:
{"id": "accounts/123456/locations/123456", "name": "Business 123 Main St Somewhere NM 10010"}
The location_metrics function looks like:
def get_metrics(loc):
request_body = {
"locationNames": [ loc['id'] ],
"basicRequest" : {
"metricRequests": [
{
"metric": 'ALL',
"options": ['AGGREGATED_DAILY']
}
],
"timeRange": {
"startTime": start_time_range,
"endTime": end_time_range,
},
}
}
request_url = <request url>
report_insights_response = http.request(request_url, "POST", body=json.dumps(request_body))
report_insights_response = report_insights_response[1]
report_insights_response = report_insights_response.decode().replace('\\n','')
report_insights_json = json.loads(report_insights_response)
<bunch of logic to parse the right metrics, am not including because this runs in a separate manual script without issue>
my_data = json.dumps(my_data)
project_id = "project_id"
topic_name = "location-metrics"
topic_id = "projects/project_id/topics/location-metrics"
publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path(project_id, topic_name)
print("publisher: ", publisher)
print("topic_path: ", topic_path)
try:
publisher.publish(topic_path, data=gmb_data.encode('utf-8'))
print("publish successful: ", loc['name'])
except Exception as exc:
print("topic publish failed: ", exc)
def retrieve_location(event, context):
auth_flow()
message_obj = event.data
message_dcde = message_obj.decode('utf-8')
message_json = json.loads(message_dcde)
get_metrics(message_json)

How do get the Linked_Account_Name while calling the cost explorer API

I have the below code to get the cost explorer details using boto3 which will give the data on the basis of account_id.I need the details on the basis of Linked_account_Name. Can someone guide me how to proceed..
response = ce.get_cost_and_usage(
TimePeriod={
'Start': '2020-01-01',
'End': '2020-01-03'
},
Granularity='MONTHLY',
Metrics=[
'UnblendedCost',
],
GroupBy=[
{
'Type': 'DIMENSION',
'Key': 'LINKED_ACCOUNT'
},
]
LINKED_ACCOUNT_NAME is not valid for all three context(COST_AND_USAGE','RESERVATIONS','SAVINGS_PLANS).
Dimensions are also limited to LINKED_ACCOUNT , REGION , or RIGHTSIZING_TYPE in get_cost_and_usage()
So, you won't be able to use it.
You can use
get_dimension_values()
use this link for more info
function to get the Linked Account name.
client = session.client('ce')
response = client.get_dimension_values(
SearchString='123456789098',
TimePeriod={
'Start': '2020-01-01',
'End': '2020-03-01'
},
Dimension='LINKED_ACCOUNT',
Context='COST_AND_USAGE'
)
for each in response['DimensionValues']:
print('Account Name is ->', each['Attributes']['description'])
and output will be like below:
Account Name is -> Test 0100
Its not a complete answer but you can proceed from here.

AWS Textract InvalidParameterException

I have a .Net core client application using amazon Textract with S3,SNS and SQS as per the AWS Document , Detecting and Analyzing Text in Multipage Documents(https://docs.aws.amazon.com/textract/latest/dg/async.html)
Created an AWS Role with AmazonTextractServiceRole Policy and added the Following Trust relation ship as per the documentation (https://docs.aws.amazon.com/textract/latest/dg/api-async-roles.html)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "textract.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Subscribed SQS to the topic and Given Permission to the Amazon SNS Topic to Send Messages to the Amazon SQS Queue as per the aws documentation .
All Resources including S3 Bucket, SNS ,SQS are in the same us-west2 region
The following method shows a generic error "InvalidParameterException"
Request has invalid parameters
But If the NotificationChannel section is commented the code is working fine and returning the correct job id.
Error message is not giving a clear picture about the parameter. Highly appreciated any help .
public async Task<string> ScanDocument()
{
string roleArn = "aws:iam::xxxxxxxxxxxx:instance-profile/MyTextractRole";
string topicArn = "aws:sns:us-west-2:xxxxxxxxxxxx:AmazonTextract-My-Topic";
string bucketName = "mybucket";
string filename = "mytestdoc.pdf";
var request = new StartDocumentAnalysisRequest();
var notificationChannel = new NotificationChannel();
notificationChannel.RoleArn = roleArn;
notificationChannel.SNSTopicArn = topicArn;
var s3Object = new S3Object
{
Bucket = bucketName,
Name = filename
};
request.DocumentLocation = new DocumentLocation
{
S3Object = s3Object
};
request.FeatureTypes = new List<string>() { "TABLES", "FORMS" };
request.NotificationChannel = channel; /* Commenting this line work the code*/
var response = await this._textractService.StartDocumentAnalysisAsync(request);
return response.JobId;
}
Debugging Invalid AWS Requests
The AWS SDK validates your request object locally, before dispatching it to the AWS servers. This validation will fail with unhelpfully opaque errors, like the OP.
As the SDK is open source, you can inspect the source to help narrow down the invalid parameter.
Before we look at the code: The SDK (and documentation) are actually generated from special JSON files that describe the API, its requirements and how to validate them. The actual code is generated based on these JSON files.
I'm going to use the Node.js SDK as an example, but I'm sure similar approaches may work for the other SDKs, including .NET
In our case (AWS Textract), the latest Api version is 2018-06-27. Sure enough, the JSON source file is on GitHub, here.
In my case, experimentation narrowed the issue down to the ClientRequestToken. The error was an opaque InvalidParameterException. I searched for it in the SDK source JSON file, and sure enough, on line 392:
"ClientRequestToken": {
"type": "string",
"max": 64,
"min": 1,
"pattern": "^[a-zA-Z0-9-_]+$"
},
A whole bunch of undocumented requirements!
In my case the token I was using violated the regex (pattern in the above source code). Changing my token code to satisfy the regex solved the problem.
I recommend this approach for these sorts of opaque type errors.
After a long days analyzing the issue. I was able to resolve it .. as per the documentation topic only required SendMessage Action to the SQS . But after changing it to All SQS Action its Started Working . But Still AWS Error message is really misleading and confusing
you would need to change the permissions to All SQS Action and then use the code as below
def startJob(s3BucketName, objectName):
response = None
response = textract.start_document_text_detection(
DocumentLocation={
'S3Object': {
'Bucket': s3BucketName,
'Name': objectName
}
})
return response["JobId"]
def isJobComplete(jobId):
# For production use cases, use SNS based notification
# Details at: https://docs.aws.amazon.com/textract/latest/dg/api-async.html
time.sleep(5)
response = textract.get_document_text_detection(JobId=jobId)
status = response["JobStatus"]
print("Job status: {}".format(status))
while(status == "IN_PROGRESS"):
time.sleep(5)
response = textract.get_document_text_detection(JobId=jobId)
status = response["JobStatus"]
print("Job status: {}".format(status))
return status
def getJobResults(jobId):
pages = []
response = textract.get_document_text_detection(JobId=jobId)
pages.append(response)
print("Resultset page recieved: {}".format(len(pages)))
nextToken = None
if('NextToken' in response):
nextToken = response['NextToken']
while(nextToken):
response = textract.get_document_text_detection(JobId=jobId, NextToken=nextToken)
pages.append(response)
print("Resultset page recieved: {}".format(len(pages)))
nextToken = None
if('NextToken' in response):
nextToken = response['NextToken']
return pages
Invoking textract with Python, I received the same error until I truncated the ClientRequestToken down to 64 characters
response = client.start_document_text_detection(
DocumentLocation={
'S3Object':{
'Bucket': bucket,
'Name' : fileName
}
},
ClientRequestToken= fileName[:64],
NotificationChannel= {
"SNSTopicArn": "arn:aws:sns:us-east-1:AccountID:AmazonTextractXYZ",
"RoleArn": "arn:aws:iam::AccountId:role/TextractRole"
}
)
print('Processing started : %s' % json.dumps(response))

Getting "InvalidParameterValue - Missing final '#domain'" from Amazon SES while sending an email with unicode characters in destination address

Amazon SES returns the error mentioned above when i try to send an email that contains unicode characters in the To: field. Amazon SES Documentation says that such email addresses should be sent in MIME encoded-word syntax, which the mail gem (used by ActionMailer) is doing correctly, it is sent as: =?UTF-8?B?dmluYXl2aW5heeKAmXNAbWFpbGluYXRvci5jb20=?=
I was seeing this same error, and found it was due to an incorrect ReturnPath parameter. The error indicates your ReturnPath parameter does not have a domain name. The ReturnPath parameter should be an email address, and it's the address to which bounce notifications are forwarded.
The question is pretty old, but I'll add my case for it seems to be the first result searching for problems with "Missing finale #domain" on AWS SES.
(the only other SO question I found is AWS SES Missing final '#domain' PHP SDK
)
As in the other question's answer, InvalidParameterValue is returned every time a parameter don't pass validation.
In my case I was using boto3 on python, composing the Destination parameter with some keys that could be empty, like so:
to = []
bcc = []
# Some code to populate one or both lists..
response = client.send_email(
Destination={
'ToAddresses': to,
'BccAddresses': bcc
},
Message={
'Body': {
'Html': {
'Charset': MAIL_CHARSET,
'Data': message,
},
'Text': {
'Charset': MAIL_CHARSET,
'Data': message,
},
},
'Subject': {
'Charset': MAIL_CHARSET,
'Data': subject,
},
},
Source=MAIL_SENDER,
)
If one of the two keys in the dict assigned to the Destination parameter was an empty list the InvalidParameterValue was returned.
Solution is to simply remove empty, useless, key:
to = []
bcc = []
# Some code to populate one or both lists..
destinations = {
'ToAddresses': to,
'BccAddresses': bcc
}
response = client.send_email(
Destination={typ: addresses
for typ, addresses in destinations.iteritems()
if addresses},
Message={
'Body': {
'Html': {
'Charset': MAIL_CHARSET,
'Data': message,
},
'Text': {
'Charset': MAIL_CHARSET,
'Data': message,
},
},
'Subject': {
'Charset': MAIL_CHARSET,
'Data': subject,
},
},
Source=MAIL_SENDER,
)
I just got this error because I was storing email addresses in a .cfg file (which uses the same structure as a Windows .ini file) and I put quotes around the email address like programmers are in the habit of doing with strings but that you shouldn't do in a cfg file:
[email_settings]
email_to="some_user#domain.com"
When I removed the quotes, it worked. I'm sure there are multiple issues that can cause this error.
I was getting this error when using the SES SendRawEmail API because my entire "To" header was being encoded in MIME encoded-words syntax as a unit.
It's not completely clear from the documentation, but it hints that the sender name should be encoded separately.
The sender name (also known as the friendly name) may contain non-ASCII characters. These characters must be encoded using MIME encoded-word syntax, as described in RFC 2047. MIME encoded-word syntax uses the following form: =?charset?encoding?encoded-text?=.
source: https://docs.aws.amazon.com/ses/latest/APIReference/API_SendRawEmail.html#:~:text=The%20sender%20name,encoded%2Dtext%3F%3D.
Python's email.message.Message class automatically encodes headers containing non-ascii characters as a single chunk:
from email.message import Message
message1 = Message()
message1["To"] = "RecipiƩnt <foo#bar.com>"
print(message1.as_string())
# To: =?utf-8?b?UmVjaXBpw6ludCA8Zm9vQGJhci5jb20+?=
I worked around this by formatting the address in advance using the formataddr utility function, which does the encoding for the sender name separately:
from email.utils import formataddr
message2 = Message()
message2["To"] = formataddr(("RecipiƩnt", "foo#bar.com"))
print(message2.as_string())
# To: =?utf-8?q?Recipi=C3=A9nt?= <foo#bar.com>