Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SESv2 Support with AWS SDK v3 #1430

Open
adamprescott opened this issue Jun 27, 2022 · 43 comments
Open

SESv2 Support with AWS SDK v3 #1430

adamprescott opened this issue Jun 27, 2022 · 43 comments
Labels

Comments

@adamprescott
Copy link

Just wondering if there are any plans to add SESv2 Support with the v3 SDK?

If not, I'll have a go at submitting a PR to update ses-transport to detect the use of the V2 API with the V3 SDK.

My current work-around is this:

const Nodemailer = require('nodemailer');
const aws = require('@aws-sdk/client-sesv2');

class SendRawEmailCommand extends aws.SendEmailCommand{
	constructor(params) {
		const input = {
			Content: {
				Raw: {
					Data: params.RawMessage.Data
				}
			},
			FromEmailAddress: params.Source,
			Destination: {
				ToAddresses: params.Destinations
			},
		};
		super(input);
	}
}

const sesClient = new aws.SESv2({
	region: process.env.AWS_REGION,
	credentials: {
		accessKeyId: process.env.SES_ACCESS_KEY_ID,
		secretAccessKey: process.env.SES_SECRET_ACCESS_KEY,
	}
});

const transport = Nodemailer.createTransport({
	SES: {
		ses: sesClient,
		aws: {
			SendRawEmailCommand: SendRawEmailCommand
		}
	}
});

// transport.sendMail....etc...
@andris9
Copy link
Member

andris9 commented Jul 6, 2022

Is there any benefit in using client-sesv2 instead of client-ses? I don't have any plans to work on it myself, but if it is something that's really required, you could make a PR for it.

@adamprescott
Copy link
Author

The SES v2 API supports e-mail messages up to 40MB in size. The v1 API only supports 10MB.
There's also support for Contact Lists, Suppression List Management and the Deliverability Dashboard.

I'll have a go at forking the project and adding it in with updated tests 👍

@github-actions
Copy link
Contributor

github-actions bot commented Aug 6, 2022

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Aug 6, 2022
@ezalorsara
Copy link

any update on this? thanks

@github-actions github-actions bot removed the stale label Aug 9, 2022
@dyaacov
Copy link

dyaacov commented Aug 14, 2022

any updates?

@adamprescott
Copy link
Author

Still on my todo list, I'll pick it up this Thursday and submit the PR 👍

@dyaacov
Copy link

dyaacov commented Aug 15, 2022

This works for me:

import AWS from 'aws-sdk';
const MailComposer = require('nodemailer/lib/mail-composer');
import fs from 'fs';
const Mustache = require('mustache');

const ses = new AWS.SESV2({
  apiVersion: '2019-09-27',
  accessKeyId: process.env.AWS_ACCESS_KEY,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: process.env.AWS_REGION,
});

const isProduction = process.env.NODE_ENV === 'production';

const xxxTemplate = fs.readFileSync(`${__dirname}/xxx.template.html`, 'utf8').toString();

const generateRawMailData = (message) => {
  let mailOptions = {
    subject: message.subject,
    html: message.bodyHtml,
    attachments: message.attachments,
  };
  return new MailComposer(mailOptions).compile().build();
};

export default class NotificationsService {
  static sendEmail = async (to, cc, bcc, subject, params, attachments = [], from = 'info@xxx.io', templateId = 'xxx') => {
    if (!isProduction) {
      to = 'dev@xxx.io';
      cc = [];
      bcc = [];
    }
    let html = '';
    let template = xxxTemplate;
    if (params.plain) {
      html = params.body;
    } else {
      html = Mustache.render(template, { ...params });
    }

    const message = {
      subject,
      bodyHtml: html,
      attachments,
    };

    params = {
      Content: { Raw: { Data: await generateRawMailData(message) } },
      Destination: {
        ToAddresses: Array.isArray(to) ? to : [to],
        BccAddresses: Array.isArray(bcc) ? bcc : [bcc],
      },
      FromEmailAddress: from,
    };

    ses.sendEmail(params).promise();
  };
}

@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Sep 15, 2022
@ihmpavel
Copy link

Hi, @adamprescott

Did you have time to dive in more in the code and make some changes?

@github-actions github-actions bot removed the stale label Sep 25, 2022
@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Oct 25, 2022
@danilo-delbusso
Copy link

Hi team 👋 . Very much waiting for this one. Any updates?

@github-actions github-actions bot removed the stale label Oct 26, 2022
@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Nov 26, 2022
@ihmpavel
Copy link

Looking forward to this one too

@github-actions github-actions bot removed the stale label Nov 27, 2022
@miller-productions
Copy link

Just passing by, this would be handy 👍

@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Jan 27, 2023
@Sparticuz
Copy link

Not stale

@github-actions github-actions bot removed the stale label Jan 28, 2023
@czhao-nsrecom
Copy link

czhao-nsrecom commented Feb 21, 2023

Any update on this? AWS has been moving away from SES v1. We also need to support large message size.

@richterdennis
Copy link

Any update on this?

@czhao-nsrecom
Copy link

czhao-nsrecom commented Mar 10, 2023

SESV2 and SDK V3 are very different from the old SDK. It would require a lot of changes by the authors to catch up. I am able to use the new SDK with mimetext to send email with attachment via SESV2.

import {createMimeMessage} from 'mimetext';

@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot removed the stale label May 23, 2023
@Sparticuz
Copy link

@andris9 can you add the 'pinned' label so github will stop making it stale?

doesn't seem to be working?

Idk, according to the GitHub action, if there is 'pinned' label, marking as stale gets skipped. I wonder if pinning the issue is different than having a label 'pinned'?

@aymanmh
Copy link

aymanmh commented Jun 13, 2023

based on dyaacov comment above (old sdk), this works flawlessly for sdk v3:

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
import MailComposer = require('nodemailer/lib/mail-composer');

const sesv2Client = new SESv2Client({ region: process.env.AWS_REGION });
/* fill the mail options as usual , my email object looks like this
    {
        "from": {"name":"Sender", "address": "example_sender@example.com"},
        "text_body": "some text",
        "subject": "Email Subject",
        "to": [{"name":" A,B", "address":"example_sender@example.com"}],
        "cc": [{"address":"example_1@example.com"},{"address":"example_1@example.com"}],
    }
*/
const mailOptions = {
        from: email.from,
        subject: email['subject'] ? email.subject : '',
        html: email['html_body'] ? email.html_body : '',
        text: email['text_body'] ? email.text_body : '',
        to: email['to'] ? email.to : '',
        cc: email['cc'] ? email.cc : '',
        bcc: email['bcc'] ? email.bcc : '',
        attachments: hasAttachments ? email.attachments : [],
      };

const rawMailData = await new MailComposer(mailOptions).compile().build();

const input = {
          Content: {
            Raw: { Data: rawMailData },
          },
        };
const cmd = new SendEmailCommand(input);

const resp = await sesv2Client.send(cmd);

@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Jul 14, 2023
@github-actions
Copy link
Contributor

This issue was closed because it has been inactive for 14 days since being marked as stale.

@richterdennis
Copy link

Pls reopen this. It is still relevant.

@andris9 andris9 reopened this Aug 25, 2023
@os-freddyshim
Copy link

Indeed, I'd still like to have a direct integration with SESv2. For now, the solution posted by @aymanmh is a fine alternative.

@github-actions github-actions bot removed the stale label Aug 26, 2023
@wirjo
Copy link

wirjo commented Sep 4, 2023

+1 - would love SES v2 support for AWS SDK v3 for 40mb file support.

@adamprescott Did you make any progress on the fork?

@andris9 andris9 added the pinned Pinned label Sep 6, 2023
@andris9 andris9 unpinned this issue Sep 6, 2023
@severi
Copy link

severi commented Sep 20, 2023

based on dyaacov comment above (old sdk), this works flawlessly for sdk v3:

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
import MailComposer = require('nodemailer/lib/mail-composer');

const sesv2Client = new SESv2Client({ region: process.env.AWS_REGION });
/* fill the mail options as usual , my email object looks like this
    {
        "from": {"name":"Sender", "address": "example_sender@example.com"},
        "text_body": "some text",
        "subject": "Email Subject",
        "to": [{"name":" A,B", "address":"example_sender@example.com"}],
        "cc": [{"address":"example_1@example.com"},{"address":"example_1@example.com"}],
    }
*/
const mailOptions = {
        from: email.from,
        subject: email['subject'] ? email.subject : '',
        html: email['html_body'] ? email.html_body : '',
        text: email['text_body'] ? email.text_body : '',
        to: email['to'] ? email.to : '',
        cc: email['cc'] ? email.cc : '',
        bcc: email['bcc'] ? email.bcc : '',
        attachments: hasAttachments ? email.attachments : [],
      };

const rawMailData = await new MailComposer(mailOptions).compile().build();

const input = {
          Content: {
            Raw: { Data: rawMailData },
          },
        };
const cmd = new SendEmailCommand(input);

const resp = await sesv2Client.send(cmd);

This worked well for us except for Bcc's. For some reason they got ignored by aws and were not sent. This was fixed by also defining the Destination attribute in addition to Content (though I decided to also set the FromEmailAddress and ReplyToAddresses attributes as well just in case after realizing this).

@github-actions
Copy link
Contributor

This issue is stale because it has been open for 30 days with no activity.

@github-actions github-actions bot added the stale label Oct 21, 2023
Copy link
Contributor

github-actions bot commented Nov 4, 2023

This issue was closed because it has been inactive for 14 days since being marked as stale.

@github-actions github-actions bot closed this as completed Nov 4, 2023
@andrefelipeschulle
Copy link

Up

@dormeiri
Copy link

dormeiri commented Nov 14, 2023

What do you think about exposing MailComposer as part of the package API?

import MailComposer = require('nodemailer/lib/mail-composer'); feels hacky, and there is no guarantee that MailComposer code won't change/move.

@marcosbarbosa031
Copy link

Based on @dyaacov comment, I adapted the code and it's working for me in a NestJS project. Sharing the code in case anyone needs it.

import { Injectable } from '@nestjs/common';
import * as AWS from 'aws-sdk';
import MailComposer from 'nodemailer/lib/mail-composer';
import { AttachmentObject } from 'nodemailer';
import logger from 'src/common/logger/logger.service';

@Injectable()
export class SESService {
  ses = new AWS.SESV2({ apiVersion: '2019-09-27' });

  async sendBatchEmails(
    files: { filename: string; buffer: Buffer }[],
    from: string,
    subject: string,
    text: string,
    emails: string[],
  ) {
    const attachments: AttachmentObject[] = files.map(
      ({ filename, buffer }) => ({
        filename,
        encoding: 'base64',
        content: buffer,
      }),
    );

    const mailOptions = {
      Content: {
        Raw: {
          Data: await this.generateRawMailData(subject, text, attachments),
        },
      },
      Destination: {
        ToAddresses: emails,
      },
      FromEmailAddress: from,
    };

    return new Promise<void>((resolve, reject) => {
      this.ses.sendEmail(mailOptions, (error, data) => {
        if (error) {
          logger.error(error);
          reject(error);
        } else {
          logger.info(`Email sent: ${data}`);
          resolve();
        }
      });
    });
  }

  generateRawMailData = (
    subject: string,
    text: string,
    attachments: AttachmentObject[],
  ) => {
    const mailOptions = {
      subject,
      html: text,
      attachments,
    };
    return new MailComposer(mailOptions).compile().build();
  };
}

@andris9 andris9 reopened this Dec 13, 2023
@andris9 andris9 added pending and removed stale pinned Pinned labels Dec 13, 2023
@jampy
Copy link

jampy commented Feb 13, 2024

According to https://docs.aws.amazon.com/ses/latest/dg/quotas.html the 40 MB limit is also available when using SMTP and Nodemailer supports SMTP.

Are there any disadvantages to use SMTP until Nodemailer supports SESv2 natively?

@JannesV
Copy link

JannesV commented Feb 13, 2024

According to https://docs.aws.amazon.com/ses/latest/dg/quotas.html the 40 MB limit is also available when using SMTP and Nodemailer supports SMTP.

Are there any disadvantages to use SMTP until Nodemailer supports SESv2 natively?

For us the issue was authentication, with SMTP you have to create SMTP Credentials which for us was a no-go. We're using it in a NestJS context and I use the following workaround to use the v2 api.

import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';

    MailerModule.forRootAsync({
      useFactory: (
        configService: ConfigService
      ) => {
        class SendRawEmailCommand extends SendEmailCommand {
          constructor(params) {
            const input = {
              Content: {
                Raw: {
                  Data: params.RawMessage.Data,
                },
              },
              FromEmailAddress: params.Source,
              Destination: {
                ToAddresses: params.Destinations,
              },
            };
            super(input);
          }
        }

        return {
          transport: {
            SES: {
              ses: new SESv2Client({
                region: configService.get('AWS_REGION'),
                credentials: fromContainerMetadata(),
              }),
              aws: {
                SendRawEmailCommand,
              },
            },
          },
        };
      },
      inject: [ConfigService],
    })

@Kenblair1226
Copy link

According to AWS SES blog: https://aws.amazon.com/blogs/messaging-and-targeting/using-one-click-unsubscribe-with-amazon-ses/. In order to use one-click unsubscribe with SES to compliant with bulk email sending rules, need to utilize ses v2 api. Might be another reason for nodemailer to support SESv2.

@DavidMarciel
Copy link

DavidMarciel commented Apr 10, 2024

based on dyaacov comment above (old sdk), this works flawlessly for sdk v3:

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
import MailComposer from 'nodemailer/lib/mail-composer/index.js';

const sesv2Client = new SESv2Client({ region: process.env.AWS_REGION });
/* fill the mail options as usual , my email object looks like this
    {
        "from": {"name":"Sender", "address": "example_sender@example.com"},
        "text_body": "some text",
        "subject": "Email Subject",
        "to": [{"name":" A,B", "address":"example_sender@example.com"}],
        "cc": [{"address":"example_1@example.com"},{"address":"example_1@example.com"}],
    }
*/
const mailOptions = {
        from: email.from,
        subject: email['subject'] ? email.subject : '',
        html: email['html_body'] ? email.html_body : '',
        text: email['text_body'] ? email.text_body : '',
        to: email['to'] ? email.to : '',
        cc: email['cc'] ? email.cc : '',
        bcc: email['bcc'] ? email.bcc : '',
        attachments: hasAttachments ? email.attachments : [],
      };

const rawMailData = await new MailComposer(mailOptions).compile().build();

const input = {
          Content: {
            Raw: { Data: rawMailData },
          },
        };
const cmd = new SendEmailCommand(input);

const resp = await sesv2Client.send(cmd);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests