Donal O'Keeffe
Donal O'Keeffe

Reputation: 47

AWS Lambda SNS sending topic twice

TL;DR

Writing a lambda function to perform some DB queries and then email certain groups of users.

Members are receiving the email twice.

The app is a music streaming app where users can create songs. They can also create groups, invite members to those groups and share their songs to these groups.

Here is the lambda called via API:

  const shareWithGroup = async event => {
  const { songCuid, groupCuids } = JSON.parse(event.body);
  const shareSongDB = await query(
    sql.queryShareWithGroup(songCuid, groupCuids),
  ); //share to group in DB
  if (!shareSongDB) {
    return corsUtil.failureWithCors("Couldn't Share Song with group");
  }
  const song = await query(sql.queryRead(songCuid));
  if (!song) {
    return corsUtil.failureWithCors('Song doesnt exist');
  }
  const songTitle = song.rows[0].songTitle; //retrieve songTitle
  const promises = groupCuids.map(async groupCuid => {
    console.log('GROUP_CUID', groupCuid);
    const emailResults = await query(sql.queryReadGroupEmails(groupCuid)); // get emails for group + groupName
    const results = emailResults.rows;
    const groupName = results[0].groupName;
    let emails = [];
    results.map(row => {
      emails.push(row.email); //push email address into array
    });
    const payload = JSON.stringify({ groupName, emails, songTitle }); //send groupName, emails list and songTitle to SNS to trigger email
    console.log(payload);
    await publishSNS(payload)
    //send the topic
  });

  //Resolve all promises
  await Promise.all(promises);
  console.log(promises);
  return corsUtil.successWithCors('Success');
};

const publishSNS = async payload => {
  console.log('publishing sns topic');
  //SEND EMAILS
  const params = {
    Message: payload,
    TopicArn: `arn:aws:sns:eu-west-1:${process.env.AWS_ACC_ID}:${process.env.STAGE}-songShareTrigger`,
  };

  return await sns
    .publish(params, async error => {
      if (error) {
        console.error(error);
        //TODO: Actually fail the function - can't do with lambdaFactory
      }
    })
    .promise();
};

And a sample request (share a song to 1 group):

{"songCuid":"XXX","groupCuids":["XXX"]}

The issue is, that somehow, even though it should only send 1 SNS topic, it sends 2. When I share a song to 2 groups, it sends 4 SNS topics.

Here is the lambda that is triggered by the SNS topic:

const aws = require('aws-sdk');
const ses = new aws.SES();
const corsUtil = require('../utils/corsUtil');

exports.songShareEmail = (event, context) => {
  console.log('EVENT : ', event.Records[0].Sns); // LOG SNS
  const body = JSON.parse(event.Records[0].Sns.Message);
  const { groupName, emails, songTitle } = body;
  console.log('Body : ', body);

  const groupInviteEmailData = `
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
      <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
      </head>
      <body>
        <div class="email-body" style="max-width:900px; margin:auto;" >
          <div class="content" style="background-color:white; margin:0 auto; 
            display:block;">
            <p>Hello<p>
            </br>
            A new song has been shared to the group : <u>${groupName}</u><br>
            <h3>Song Title: ${songTitle}</h3>

          </div>
        </div>
      </body>
      </html>
  `;

    var params = {
        Destination: {
          ToAddresses: emails,
        },
        Message: {
          Body: {
            Html: {
              Charset: 'UTF-8',
              Data: groupInviteEmailData,
            },
          },
          Subject: {
            Charset: 'UTF-8',
            Data: `New song shared to ${groupName}`,
          },
        },
        Source: 'xxx',
      };
      console.log('sendingEmail');
      ses.sendEmail(params, function(err) {
        if (err) {
          console.log(err);
          const response = corsUtil.failureWithCors(err);
          context.fail(response);
        } else {
          context.succeed('Done');
        }
      });
    };

I have logged the SNS object from the event, and I can see that it is a new MessageId each time meaning that it is in fact sending 2 topics each time, and it's not that the lambda is getting triggered twice from the same SNS.

Is there any way around this?

Upvotes: 2

Views: 2021

Answers (3)

TemporaryFix
TemporaryFix

Reputation: 2186

The selected answer is half way right.

I also had this same issue. You need to either use SNS with a callback or use SNS setting the api version in the constructor and using promise()

const sns = new SNS({ apiVersion: '2010-03-31' });
await sns
  .publish(
    {
      Message: 'Message',
      TopicArn: <Arn>,
    },
  )
  .promise();

If you do not pass in the apiVersion in the constructor you have to pass in a callback and not use promise()

Upvotes: 5

xmoulin
xmoulin

Reputation: 31

I had the same pb for an iotData, i use something like this to solve my problem

const request = iotdata.publish(mqttParams);
request
    .on('success', () => console.log("Success"))
    .on('error', () => console.log("Error"))
return new Promise(() => request.send());

Upvotes: 0

WGriffing
WGriffing

Reputation: 624

I know this issue is old, but I found myself having the same problem. I solved my mistake and I believe you have the same exact problem.

The .promise() in your publishSNS method is not needed, just remove it (commented out below).

const publishSNS = async payload => {
  console.log('publishing sns topic');
  //SEND EMAILS
  const params = {
    Message: payload,
    TopicArn: `arn:aws:sns:eu-west-1:${process.env.AWS_ACC_ID}:${process.env.STAGE}-songShareTrigger`,
  };

  return await sns
    .publish(params, async error => {
      if (error) {
        console.error(error);
        //TODO: Actually fail the function - can't do with lambdaFactory
      }
    });
    //.promise(); 
};

Upvotes: 3

Related Questions