webiny/webiny-js

Mailer not sending transactional emails using AWS SES credentials

alifeinbinary opened this issue · 6 comments

Version

5.37.2

Operating System

MacOS Monterey 12.6.8

Browser

116.0.3

What are the steps to reproduce this bug?

Assuming you have a verified domain and identity in AWS SES console and satisfied the DKIM, SPF, DMARC compliance within your DNS settings for your verified domain. In AWS SES console, generate SMTP credentials and paste them into the USER and PASSWORD variables as I have done below. I used Thunderbird app to test email sending and confirmed that I am able to send using the SMTP credentials provided by Amazon. If you do the same, be sure to use STARTTLS on port 587 and Plain text authentication using username and password.

Configure your .env file like this, make sure to use port 587 for STARTTLS to work.

# Mailer configuration
WEBINY_MAILER_PASSWORD_SECRET=g8fj4a5485dfGkX12bcca898ea31faa9f2
# required
WEBINY_MAILER_HOST=email-smtp.ca-central-1.amazonaws.com
WEBINY_MAILER_PORT=587
WEBINY_MAILER_USER=AKIAGHFKDOSJSHIXXB
WEBINY_MAILER_PASSWORD=BNAop1UdDvB4MM5EypIzqrBnDznXX/eKMLDNLghWVeV0
WEBINY_MAILER_REPLY_TO=name@domain.com
WEBINY_MAILER_FROM=name@domain.com

In apps/api/graphql/src/index.ts add the following transport to enable logging and debugging in CloudWatch

const transport = createTransport(async ({ settings }) => {
    console.log("settings", settings);
    return createSmtpTransport({
        ...settings,
        from: "name@domain.com",
        envelope: {
            from: "Company <name@domain.com>"
        },
        host: "email-smtp.ca-central-1.amazonaws.com",
        port: 587,
        secure: false,
        auth: {
            user: process.env["WEBINY_MAILER_USER"],
            pass: process.env["WEBINY_MAILER_PASSWORD"]
        },
        authMethod: "PLAIN",
        requireTLS: true,
        name: "domain.com",
        logger: true,
        debug: true
    });
});

Make sure to add transport to the plugins array.

Create a basic contact form within FormBuilder with some basic fields like; first name, last name, email, message, and within the triggers tab add your email to "Email - Submission Notification" and some text to the Subject and Email content fields for the "Email - Thank You Email" trigger. Save and publish the form. Create a /contact page to place the form into it within PageBuilder and publish the page. Navigate to localhost:3000/contact and you should see the contact form.

CloudWatch
In AWS CloudWatch console select Live Tail from the menu on the left side. Select the graphql Lambda function linked to your development environment and click filter. If CloudWatch hasn't started the Live tailing process automatically then click "Start".

Return to your contact form, fill in the fields and make sure to include and email that you have access to, and submit.

What is the expected behavior?

It's expected that two emails should be sent; one submission notification and another thank you email and received at the address provided in the "Email - Submission Notification" form trigger and the address that was included in the form submission.

What do you see instead?

While the website app will report that the message was successfully sent, this won't be the case. Looking at the CloudWatch logs we can see where it failed.

2023-08-22T20:58:04.045Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] S: 250-AUTH PLAIN LOGIN

2023-08-22T20:58:04.045Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] S: 250 Ok

2023-08-22T20:58:04.046Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] SMTP handshake finished

2023-08-22T20:58:04.046Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] C: AUTH PLAIN AEFLSUFYVlJVTlRKTUVDQjJJMjVNAC5qIHNlY3JldCAqGw==

2023-08-22T20:58:04.057Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] S: 235 Authentication successful.

2023-08-22T20:58:04.057Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] INFO [LZyxdJeuXLE] User "AKIAGHFKDOSJSHIXXB" authenticated

2023-08-22T20:58:04.057Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] INFO Sending message <b36bb36c-be99-3ad4-3e06-6c724007b843@localhost> to <recipient@server.com>

2023-08-22T20:58:04.058Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] C: MAIL FROM:<>

2023-08-22T20:58:04.060Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] S: 501 Invalid MAIL FROM address provided

2023-08-22T20:58:04.061Z f56ffae3-7f6b-48e3-957c-15033184a759 INFO [2023-08-22 20:58:04] DEBUG [LZyxdJeuXLE] Closing connection to the server using "end"

So, authentication is successful, however, in the third to last CloudWatch log line we can see that MAIL FROM isn't provided by the message context data.

Additional information

According to the nodemailer documentation the transport uses the sendMail function like so:

nodemailer SMTP envelope

let message = {
    ...,
    from: 'mailer@nodemailer.com', // listed in rfc822 message header
    to: 'daemon@nodemailer.com', // listed in rfc822 message header
    envelope: {
        from: 'Daemon <deamon@nodemailer.com>', // used as MAIL FROM: address for SMTP
        to: 'mailer@nodemailer.com, Mailer <mailer2@nodemailer.com>' // used as RCPT TO: address for SMTP
    }
}

transporter.sendMail(message[, callback])

The Webiny implementation of nodemailer doesn't provide acccess to the sendMail function or the message data, so debugging further is challenging without help from the Webiny team.

Possible solution

It appears that the Webiny implementation of nodemailer isn't adhering to the SMTP envelope format, linked above. My next comment links to the points of interest in the Webiny code base and possible solutions. Please advise if you would like me to submit a PR.

If I had to guess, packages/api-mailer/src/crud/transport/onTransportBeforeSend.ts#L13

Should be:

    .object({
        to: zod.array(requiredEmail).optional(),
        from: zod.string().email().optional()
        envelope: {
            from: zod.string().email().optional(),
            to: zod.array(requiredEmail).optional()
        },
        subject: requiredString.max(1024).min(2),
        cc: zod.array(requiredEmail).optional(),
        bcc: zod.array(requiredEmail).optional(),
        replyTo: zod.string().email().optional(),
        text: zod.string().optional(),
        html: zod.string().optional()
    })

And packages/api-mailer/src/types.ts#L146

Should be:

interface BaseTransportSendData {
    to?: string[];
    from?: string[];
    cc?: string[];
    bcc?: string[];
    envelope?: {
        from?: string[];
        to?: string[];
    };
    subject: string;
    text?: string;
    html?: string;
    replyTo?: string;
}