Emails are waiting in queue when bulk emails sent.

gopu 0 Reputation points
2023-04-27T07:09:11.17+00:00

We are using Outlook graph v1.0 API to send the email in our Node js server. For sending the bulk email we are splitting the emails into chunks each containing 20 recipients with different messages.

Now consider I'm sending a bulk of 1000 recipients in a single hit with different messages.

It splits into 50 chunks of 20 messages.

Then I'm trying to hit the batch API.

Now I'll filter the failed responses with status 429 alone and retry with the same message in the retry timeout time which I got in the failed response.

Within this period of time, if any other user is trying to hit the sendMail API, it takes some time (20 minutes, 30 minutes) to receive the mail in the receiver's inbox. But in our server, we got success response while hitting the API. We are facing this issue in our live environment.

Below attached my code reference which I tried for the Outlook mail service.

From the below code for bulk email sending I'm using the sendBulkMail function after that for any single email I'm using the sendSingleMail function.

Can I please get any solution regarding this?

let tokenConfig = {
    access_token: '',
    token_type: '',
    expires_in: 0
};

const isTokenValid = () => {
    try {
        if (tokenConfig.access_token) {
            return (tokenConfig.expires_in > moment().toDate().getTime());
        }
        return false;
    } catch (error) {
        return false;
    }
};

const getToken = async () => {
    try {
        if (isTokenValid()) {
            return tokenConfig;
        }
        tokenConfig = {
            access_token: '',
            token_type: '',
            expires_in: 0
        };
        const tokenUrl = `${config.mail.smtp.azureAadEndpoint}/${config.mail.smtp.tenantId}/oauth2/token`;
        const requestBody = {
            grant_type: 'client_credentials',
            client_id: config.mail.smtp.clientId,
            client_secret: config.mail.smtp.clientSecret,
            resource: config.mail.smtp.azureGraphEndpoint
        };
        const bodyPayload = Object.keys(requestBody).map((key) => `${key}=${encodeURIComponent(requestBody[key])}`).join('&');
        const headersConfig = { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } };
        const tokenResp = await request.post('', tokenUrl, bodyPayload, headersConfig);
        tokenConfig = {
            access_token: tokenResp.data.access_token,
            token_type: tokenResp.data.token_type,
            expires_in: moment().add(tokenResp.data.expires_in, 'seconds').toDate().getTime(),
        };
        return tokenConfig;
    } catch (error) {
        tokenConfig = {
            access_token: '',
            token_type: '',
            expires_in: 0
        };
        return tokenConfig;
    }
};

const sendMail = async (mailBody, batch = false) => {
    try {
        const { access_token, token_type } = await getToken();

        if (batch) {
            const sendMailUrl = `${config.mail.smtp.azureGraphEndpoint}/${config.mail.smtp.azureGraphEndpointVersion}/$batch`;

            const headersConfig = {
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `${token_type} ${access_token}`
                }
            };
            const mailSendResp = await timeouutSendMail(sendMailUrl, mailBody, headersConfig);
            return mailSendResp;
        } else {
            const sendMailUrl = `${config.mail.smtp.azureGraphEndpoint}/${config.mail.smtp.azureGraphEndpointVersion}/users/${config.mail.fromMailId}/sendMail`;

            const headersConfig = {
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `${token_type} ${access_token}`
                }
            };
            const mailSendResp = await timeouutSendMail(sendMailUrl, mailBody, headersConfig);
            return mailSendResp;
        }
    } catch (error) {
        return Promise.reject(error);
    }
};

const retryMailTimeout = (reqBody, batch = false, retrySec = 0) => {
    return new Promise((resolve, reject) => {
        setTimeout(async () => {
            try {
                const outlookSendResp = await sendMail(reqBody, batch);
                resolve(outlookSendResp);
            } catch (error) {
                reject(error);
            }
        }, (retrySec + 1) * 1000);
    });
};

const retryMailTillSuccess = async (messages, retrySec = 0) => {
    try {
        let allMailResponses = [];
        const splitRetryMail = async (innerMessages = []) => {
            if (!innerMessages.length) { innerMessages = messages; }
            let returnResponses = [];
            const msgSplits = _.chunk(innerMessages, 20);
            for (const msgData of msgSplits) {
                const outlookSendResp = await retryMailTimeout({ requests: msgData }, true, (retrySec + 1));
                returnResponses = [...returnResponses, ...outlookSendResp.data.responses];
            }
            const retryResponses = _.filter(returnResponses, { status: 429 }).map(innerData => {
                innerData.id = Number(innerData.id);
                innerData.headers['Retry-After'] = Number(innerData.headers['Retry-After']);
                return innerData;
            });

            const maxRetrySec = Math.max(...retryResponses.map(o => o.headers['Retry-After']));

            const retryMessages = retryResponses.map(retryData => _.find(innerMessages, { id: retryData.id }));
            allMailResponses = [...allMailResponses, ..._.filter(returnResponses, respData => { return (respData.status != 429); })].filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
            if (retryMessages.length) {
                const outlookSendResp = await splitRetryMail(retryMessages, maxRetrySec);
                return outlookSendResp;
            }
            return returnResponses;
        };
        await splitRetryMail();
        return allMailResponses;
    } catch (error) {
        return Promise.reject(error);
    }
};

const sendBulkMail = async (messages = []) => {
    try {
        if (messages.length) {
            messages = messages.map((innerData, idx) => {
                innerData.from = config.mail.fromMailId;
                if (innerData.htmlTemplate) { innerData.html = innerData.htmlTemplate; }
                const mailBody = {
                    message: {
                        subject: innerData.subject,
                        body: {
                            contentType: ((innerData.htmlTemplate || innerData.html) ? 'HTML' : 'Text'),
                            content: (innerData.text || (innerData.htmlTemplate || innerData.html))
                        },
                        toRecipients: [{
                            emailAddress: {
                                address: innerData.to
                            }
                        }]
                    },
                    saveToSentItems: 'false'
                };
                if (innerData.mailAttachments && innerData.mailAttachments.length) {
                    mailBody.message.attachments = innerData.mailAttachments.map(attachementData => {
                        return {
                            '@odata.type': '#microsoft.graph.fileAttachment',
                            name: attachementData.filename,
                            contentType: attachementData.type,
                            contentBytes: attachementData.content
                        };
                    });
                }

                return {
                    id: idx,
                    url: `/users/${config.mail.fromMailId}/sendMail`,
                    method: 'POST',
                    body: mailBody,
                    headers: {
                        'Content-Type': 'application/json'
                    }
                };
            });

            const returnResponses = await retryMailTillSuccess(messages, 1);
            return returnResponses;
        }
        return [];
    } catch (error) {
        return Promise.reject(error);
    }
};

const sendSingleMail = async (toMailId, mailSubject, mailText, mailHtml, mailAttachments) => {
    try {
        if (typeof toMailId == 'string') {
            toMailId = [toMailId];
        }

        const mailBody = {
            message: {
                subject: mailSubject,
                body: {
                    contentType: (mailHtml ? 'HTML' : 'Text'),
                    content: (mailText || mailHtml)
                },
                toRecipients: toMailId.map(mailId => ({
                    emailAddress: {
                        address: mailId
                    }
                }))
            },
            saveToSentItems: 'false'
        };
        if (mailAttachments && mailAttachments.length) {
            mailBody.message.attachments = mailAttachments.map(attachementData => {
                return {
                    '@odata.type': '#microsoft.graph.fileAttachment',
                    name: attachementData.filename,
                    contentType: attachementData.type,
                    contentBytes: attachementData.content
                };
            });
        }
        const outlookSendResp = await sendMail(mailBody);
        return outlookSendResp;

    } catch (error) {
        console.log('\n mail send error...', error);
        return Promise.reject(error);
    }
};

Microsoft Security | Microsoft Graph
0 comments No comments
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.