0

I have tried sending emails on my LightSail instance via AWS SES using the following Node.js JavaScript AWS SDK code, but it failed with the following error message. The email sending code works fine on my development computer. (The email sending code is from https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/ses-examples-sending-email.html.)

(code here)

import { readFile } from 'fs/promises';
import * as path from 'path';
import { SESClient } from "@aws-sdk/client-ses";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const awsConfigFileFullName = "aws_config.json";

let awsConfigFileFullPath = path.join(__dirname, awsConfigFileFullName);

const awsConfig = await readFile(awsConfigFileFullPath).then(json => JSON.parse(json)).catch(() => null);

aws_ses_client = new SESClient({ region: awsConfig.region, accessKeyId: awsConfig.accessKeyId, secretAccessKey: awsConfig.secretAccessKey });

const createSendEmailCommand = (toAddress, fromAddress, htmlContent, textContent, emailSubject) => {
  return new SendEmailCommand({
    Destination: {
      /* required */
      CcAddresses: [
        /* more items */
      ],
      ToAddresses: [
        toAddress,
        /* more To-email addresses */
      ],
    },
    Message: {
      /* required */
      Body: {
        /* required */
        Html: {
          Charset: "UTF-8",
          Data: htmlContent,
        },
        Text: {
          Charset: "UTF-8",
          Data: textContent,
        },
      },
      Subject: {
        Charset: "UTF-8",
        Data: emailSubject,
      },
    },
    Source: emailSenderName + '<' + fromAddress + '>',
    ReplyToAddresses: [
      /* more items */
    ],
  });
};

const sendEmailCommand = createSendEmailCommand(
recipientEmailAddress,
senderEmailAddress,
htmlEmailContent,
textEmailContent,
emailSubject
);

try {
await aws_ses_client.send(sendEmailCommand);
} catch (e) {
console.error("Failed to send email.", e);
}

(error here)

AccessDenied: User arn:aws:sts::(some number):assumed-role/AmazonLightsailInstanceRole/i-(some alphanumeric number)is not authorized to perform ses:SendEmail' on resource `arn:aws:ses:us-east-1:(some number):identity/(recipient email address)'

After doing some search online on the issue, thinking that the error was caused by port 25 restriction on Lightsail instance restriction (https://aws.amazon.com/premiumsupport/knowledge-center/lightsail-port-25-throttle/), I sent a restriction removal request to AWS, but the request was declined, telling me to "consider looking into the Simple Email Service". I sent a reply asking if sending emails via AWS SES was possible on LightSail instance, but got a reply saying "we cannot grant your request"; I feel that the second reply was either automated or wasn't even thoroughly read by the person who reviewed the email.

I have a multisite WordPress installed on my LightSail webserver, which can send emails via AWS SES with WP Mail SMTP plugin, with TLS encryption using SMTP port 587. I think this is a proof that emails can be sent on a LightSail instance via AWS SES. Emails do get sent by my WordPress on my LightSail webserver, everyday, using my AWS SES SMTP credentials; so, maybe the AWS SES SMTP server must be directly contacted in the LightSail instance email sending code to send emails, instead of using the authenticated SES client object in the code?

I'm thinking that maybe the assumed role AmazonLightsailInstanceRole doesn't have AWS SES email sending allowed. I've checked my AWS IAM web console, and there was no role named AmazonLightsailInstanceRole; it doesn't look like I can modify the policy on the assumed role AmazonLightsailInstanceRole.

Can AWS SES email-sending permission be granted to the assumed role AmazonLightsailInstanceRole? If so, how?

Is it possible to send emails via AWS SES on an AWS LightSail instance? If so, how can it be done in Node.js JavaScript AWS SDK code? Any verifications, references and/or directions would be very helpful.

Coder1979
  • 107
  • 9

2 Answers2

1

Sounds like an IAM issue - the instance role is the fallback option if you dont pass your own credentials. Lines 6..8 of the code is looking for a file (aws_config.json) which contains an API key and secret and a default region, then on line 9 its passing those values into SESClient. When you finally call aws_ses_client.send(sendEmailCommand) it will use the instance default credentails provided by lightsail which wont have access to resources in your aws account like SES. Check the following:

  • Does file aws_config.json exist?
  • Does it contain api key/secret/region for a valid user in your AWS account?
  • Does that user have a suitable IAM policy including ses:SendEmail and possibly other iam permissions?

FYI catch(() => null) could be hiding an error - you should use console.log in your catch() statements to write an error (at least while your debugging).

MisterSmith
  • 2,884
  • 1
  • 10
  • 13
  • aws_config.json exists, and all the info is passed to SESClient correctly (verified via console.log()). ses:SendEmail permission is granted to the user; as I've noted, the code works on my development computer. Is it possible to attach a custom role and policy to an AWS LightSail instance, so that it has SES send email permission? If so, how to do it? – Coder1979 Feb 01 '23 at 19:24
  • I dont see any way of modifying the IAM role in lightsail. and the underlying aws resources are all in a different account. It might be possible to assume a role using the instance profile credentials - but i dont see a good way of locking this down from IAM. There are no conditions or trust policies that accept cloudsail in IAM console etc. Try `sts.getCallerIdentity` on your dev and cloudsail - confirm what creds are being used - "it works on my machine" is rarely a good defense :-) The call is failing because its using the instance profile not the creds specified. – MisterSmith Feb 01 '23 at 20:25
  • "it works on my machine" simply means it works in one environment; it doesn't necessarily mean it will work elsewhere; still, I do know that the code works in one place in a right environment. https://lightsail.aws.amazon.com/ls/docs/en_us/all/articles has some useful info about AWS LightSail, including LightSail being separate from all the other AWS services, and LightSail using a default, assumed IAM role and policy that cannot be modified; it looks like LightSail is designed to block using other AWS services. – Coder1979 Feb 01 '23 at 21:07
  • Either I'll have to find a way for accessing AWS SES and S3 from a LightSail instance, or if it isn't possible or too time-consuming, I'll have to deploy my Node.js app elsewhere such as AWS Lambda. Thanks for your input. – Coder1979 Feb 01 '23 at 21:07
  • I'm surprised you cant add a custom policy, or allow a role to be assumed from lightsail tbh but i found similar. If you can link to an S3 bucket in your own account, you could setup notifications to trigger a lambda or send the message to SES via SNS. if the bucket is in lightsails account i dont think this will work either :-/ – MisterSmith Feb 01 '23 at 21:19
  • Now I understand why it is called 'Light'Sail. I'm considering other deployment options--EC2, Elastic Beanstalk, Lambda, Fargate, etc. I realize that it's finally the time for me to graduate from 'Light'Sail and move onto something bigger, a real enterprise web app production or deployment environment. Thanks again for all you input; it clarified the issue for me. I'm accepting your answer (as the most helpful in solving the problem). – Coder1979 Feb 01 '23 at 21:26
0

UPDATE

While testing my Node.js Express web app on an EC2 instance, to resolve the error I encountered, I came across the following: "Could not load credentials from any providers" while using dynamodb locally in Node.

Because I was using @aws-sdk/ses-client (version 3), not version 2, I should've used the following:

aws_s3_client = new S3Client({ region: awsConfig.region, credentials: {accessKeyId: awsConfig.accessKeyId, secretAccessKey: awsConfig.secretAccessKey }});
aws_ses_client = new SESClient({ region: awsConfig.region, credentials: {accessKeyId: awsConfig.accessKeyId, secretAccessKey: awsConfig.secretAccessKey }});

Using the above code enabled using both AWS SES and S3 from my web app running on a LightSail instance. So, the answer is, yes, it is possible to send emails via AWS SES on AWS LightSail instance.

Coder1979
  • 107
  • 9