Getting timeout from Mailchimp Transactional when running from Lambda function - node.js

I'm trying to send emails through Mailchimp Transactional/Mandrill using Node and Serverless Framework.
I'm able to send emails fine locally (using serverless-offline), however when I deploy the function to our staging environment, it is giving a timeout error when trying to connect to the API.
My code is:
const mailchimp = require('#mailchimp/mailchimp_transactional')(MAILCHIMP_TRANSACTIONAL_KEY);
async function sendEmail(addressee, subject, body) {
const message = {
from_email: 'ouremail#example.com',
subject,
text: body,
to: [
{
email: addressee,
type: 'to',
},
],
};
const response = await mailchimp.messages.send({ message });
return response;
}
My Lambda is set at a 60 second timeout, and the error I'm getting back from Mailchimp is:
Response from Mailchimp: Error: timeout of 30000ms exceeded
It seems to me that either Mailchimp is somehow blocking traffic from the Lambda IP, or AWS is not letting traffic out to connect to the mail API.
I've tried switching to use fetch calls to the API directly instead of using the npm module, and still get back a similar error (although weirdly in html format):
Mailchimp send email failed: "<html><body><h1>408 Request Time-out</h1>\nYour browser didn't send a complete request in time.\n</body></html>\n\n"
Are there any AWS permissions I've missed, or Mailchimp Transactional/Mandrill configs I've overlooked?

I was having the identical issue using Mailchimp's Marketing API and solved it by routing traffic through an NAT Gateway. Doing this allows your lambda functions that are within a VPC to reach external services.
The short version of how I was able to do this:
Create a new subnet within your VPC
Create a new route table for the new subnet you just created and make sure that the new subnet is utilizing this new route table
Create a new NAT Gateway
Have the new route table point all outbound traffic (0.0.0.0/0) to that NAT Gateway
Have the subnet associated with the NAT Gateway point all outbound traffic to an Internet Gateway (this is generally already created when AWS populates the default VPC)
You can find out more at this link: https://aws.amazon.com/premiumsupport/knowledge-center/internet-access-lambda-function/

Related

CORS: PreflightMissionAllowOriginHeader aws API gateway lambda

I have node/express APIs running in Lambda function.
The API endpoint is {domain}/api/user/{username} where I pass username in URL itself.
example: to get userA detail, endpoint will be xxx.com/api/user/userA
also sending user_id:xxx in header.
Hitting above endpoint using API gateway/Lambda returns the data without any error.
Problem occurs when I use % in username.
Assume I have username as userA% .
Endpoint would become: xxx.com/api/user/userA%
Now, the problem is when I run this in my local machine with node/express/mysql api with endpoint localhost:2000/api/user/userA%, it returns the data.
But the same API using API gateway/Lambda : xxx.com/api/user/userA% throws
CORS: PreflightMissionAllowOriginHeader.
I have configured some CORS policies as shown in below image but can't seem to figure out what should I configure more to allow this type of requests.

Not getting response to DocuSign webhook listener url

For getting envelop status, I followed these steps
docusign developer account, under connect, I created a connect with webhook url.
In code , I have given eventNotification with webhook listener url with https:// address of my application.
I am getting response in connect logs. But I am not getting any response in my application webhook listner .why?
I have used Laravel code, with route
Route::post('webhook', [TestController::class, 'webhook']);
But I am not getting any response in function?why?
Ensure that your server (the "listener") has an https URL that is visible (callable) from the public internet. Best way to confirm this: implement a GET function at a URL such as https://your-server.com/hello. It should reply with "hello" when called. Then try entering that URL on your mobile phone.
Look at DocuSign's Connect error log to see what it says.
To assure yourself that DocuSign is sending notification messages, first use https://webhook.site to get a test webhook listener URL, and configure DocuSign to use it.
If you feel that the messages are being sent to your server but are not being received by your software, check your web server's logs. For example, if you're including documents in your notifications, the notifications will be very large and may exceed your web server's default configuration limits.
One issue which I have found, the response which is sent from Webhook to our own custom API, which will receive request from webhook does not match
For e.g.argument webhook sends json payload , so make sure you have same object which is supported by your api from docusign connect
// this is C# code
public async Task Post([FromBody] JObject envelopeData)
To test locally, you can use ngrock, which will create local server but you will be able to debug
You can try something as below. The same worked for me.
if (!Request.Body.CanSeek) { Request.EnableBuffering(); }
Request.Body.Position = 0;
var reader = new StreamReader(Request.Body, Encoding.UTF8);
var body = await reader.ReadToEndAsync().ConfigureAwait(false);

Send GET request from Amplify service to EC2 machine

< I am a real newbie so I am sorry if I am using the terms incorrectly. >
Hey guys!
I am trying to deploy my website, I put my front - files in Amplify app which provides me with an HTTPS url.
My goal is to load my backend code to EC2 ubuntu machine and to run it via pm2.
I have a trouble understanding how to do it, I am writing backend code in nodejs and I am using the express framework.
When I am developing locally, it all runs perfectly.
My backend code :
app.get('/db', (req,res) => {
let ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
const params = {
TableName: "blablabla",
};
let itemObj = [];
ddb.scan(params, function (err, data) {
if (err) {
console.log("Error", err);
} else {
console.log("Success", data);
data.Items.forEach(function (element, index, array) {
itemObj.push(data.Items);
res.status(200).json(itemObj);
});
}
})
Relate front-end code :
function getData(username){
var xmlhttp = new XMLHttpRequest();
var url = "http://localhost/db";
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) { //request completed
result = JSON.parse(this.responseText);
blablabla
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
When I am using localhost url and run the server via my computer (npm start server..) I do get the data I am looking for on the amplify service.
But when I use the elastic IP addresses of the EC2 machine I get an error: "was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint, This request has been blocked"
Is there any way to allow those kind of requests?
Do I even use the correct IP of the EC2 machine?
It seems to me that if EC2 provided me an HTTPS address, it will works fine, am I right or it has nothing to do with it?
Thanks in advence.
It works on your local machine because you don't have an SSL certificate on localhost, so your frontend is not loaded over a secure connection. When you run the frontend from Amplify, you're connecting to the Amplify domain name via SSL (I expect the URL is something like https://master.randomalphanumericstring.amplifyapp.com). So your browser complains when that page tries to make an insecure connection to your EC2 instance.
You can work around this by changing your browser settings to allow mixed content. In Chrome, it's Settings->Site Settings->Insecure Content->Add site. But that's just a workaround for development, obviously that won't work for production.
You can't make an HTTPS request to an IP address. SSL certificates must be associated with a domain name. It's also not a very robust solution to have your backend depend on a specific IP address. You have a few options to address this:
Generate an SSL certificate and install it on your EC2 instance. You can't use AWS Certificate Manager with EC2, so you'd need to obtain a certificate from letsencrypt or some other source. Self-signed won't work, it has to be trusted by the browser. And of course you need a registered domain for that.
Add an application load balancer with a secure listener and a certificate issued through ACM that directs requests to your EC2 instance. Again, you'll need to have a registered domain that you can use with the certificate.
Deploy your backend through Amplify. This will provide an API endpoint with a secure connection in the awsamazon.com domain.
There are many other ways to create an app backend with a secure connection, but those should get you started.

AWS Api Gateway not setting CORS on 204

I've built a frontend application with Angular which uses an API backend hosted on AWS ECS, provided by AWS Api Gateway.
So, this is my setup:
/user/{userId}/account/{accountId}/dashboard/{proxy+}
is the endpoint on API Gateway
it's using an AWS Lambda proxy integration for the OPTIONS method, which currently only checks if the origin is allowed or not to proceed
the GET method instead uses a custom AWS Lambda authorizer within the Method Request part, then it proceeds to the Integration Request part with a VPC Link to the ECS microservice and finally goes back to the Method Response part.
Currently possible HTTP status codes are: 200, 204, 401, 500, 504 and only 204 and 504 are set here (sincerly I don't know if it does something or not)
this is the Node.js Lambda authorizer relevant code:
const UNAUTHORIZED_STRING = "Unauthorized";
exports.handler = (event, context, callback) => {
/* Preliminar checks here */
const keyRequiresAuth = xApiKeyRequiresAuth(xApiKey);
if (keyRequiresAuth) {
// try validating using cookie
// uses generatePolicy at the end
userAuthentication(cookieValue, event, callback);
} else {
// Validate using x-api-key
const generatedPolicy = generatePolicy(xApiKey, 'Allow', event.methodArn);
callback(null, generatedPolicy);
}
};
const generatePolicy = (principalId, policyEffect, resource) => {
const authResponse = {
principalId: principalId
};
if (policyEffect && resource) {
authResponse.policyDocument = {
Version: '2012-10-17',
Statement: [{
Action: 'execute-api:Invoke',
Effect: policyEffect,
Resource: resource
}]
};
}
return authResponse;
};
Assuming that Microservices are not setting any headers at all, the problem is, while I made it with 401 and 504 status codes by setting them as a default gateway response, how do I manage to return CORS with 204?
IMHO, I think API Gateway has the most complex system to set error responses, but, apart from that, I managed to let it return CORS with a 401 Unauthorized error
Update
I made it with http status 500 aswell, but by setting a default Gateway response
So, after two days of testing I came out with a solution. I mean, probably I've misunderstood something inside AWS API Gateway, but I saw that my endpoint was actually referencing to a VPC Link which was pointing at my ECS microservice.
Only thing I had to do was update that microservice with CORS response headers,
that fixed this issue
If you're using a proxy integration, setting CORS in API Gateway is not enough. The response from Lambda has to include the headers.
To enable CORS for the Lambda proxy integration, you must add Access-Control-Allow-Origin:domain-name to the output headers. domain-name can be * for any domain name.
source: Set up Lambda proxy integrations

AWS lambda basic-authentication with Application Load Balancer

A couple of days ago I asked AWS lambda basic-authentication without custom authorizer. I got the answer which was enough for me, I implemented the custom authorizer which works properly.
I have a very similar problem right now because we decided to change API Gateway to Application Load Balancer which will trigger the lambda function on an appropriate path.
I would like to prepare the basic authentication for this endpoint also (exact the same as before).
So, the same problem:
AWS lambda function which is a proxy for an additional service. This function only forwards the whole request and give the user the whole response. That's why I need to force the usage of the Authentication header and I would like to have the prompt window for passing the credentials: Authentication.
The response which should be sent from the lambda function is a little bit different than for the API Gateway: Using AWS Lambda with an Application Load Balancer - AWS Lambda
About the authentication with the usage of ALB, I found only Authenticate Users Using an Application Load Balancer - Elastic Load Balancing.
I can't find anything connected with Basic Authentication and the prompt window.
Has anyone ever tried to setup basic auth with ALB for the lambda function? Where to look for the information?
To answer my own question:
I started my search for an answer in the wrong places. I thought that it should be connected with the ALB but in the end, it was not that hard as I thought at the beginning. It can work as a simple Basic Authentication.
So, it is enough to return that response from the asynchronous function/handler to do that in the simplest way:
{
statusCode: 401,
statusDescription: "401 Unauthorized",
isBase64Encoded: false,
headers: { "content-type": "application/json", "WWW-Authenticate": "Basic" },
body: "",
};
Of course, there is a possibility to return anything that you want in the body of this response.

Resources