Lambda function throwing 502 error in browser while working correctly, returning 200 in Netlify function log

I’m having an issue with a simple lambda function that sends an email via mailgun — the function returns 200 and works correctly but a 502 error is shown in the client side console.

The site name is twelvenyc.netlify.app.

Here’s the function source code:

// Gatsby settings for the environment variables
require("dotenv").config();
const headers = {
	"Access-Control-Allow-Origin": "*",
	"Access-Control-Allow-Headers": "Content-Type",
        "Access-Control-Allow-Credentials": "true",
};
const successCode = 200;
const errorCode = 400;

// Connect to our Mailgun API
const mailgun = require("mailgun-js");
const mg = mailgun({
	apiKey: process.env.MAILGUN_API_KEY,
	domain: process.env.MAILGUN_DOMAIN,
});

// Our Netlify function
export function handler(event, context, callback) {
	console.log(mg);
	let data = JSON.parse(event.body);
	let { name, companyName, fromEmail, phoneNumber, message, toEmail } = data;

	let bodyText = `Name: ${name}
Company: ${companyName}
Email: ${fromEmail}
Phone Number: ${phoneNumber}

${message}`;

	let mailOptions = {
		from: `${name} <${fromEmail}>`,
		to: toEmail,
		replyTo: fromEmail,
		subject: "twelvenyc.com Contact Form Submission",
		text: `${bodyText}`,
	};

	// Our MailGun code
	mg.messages().send(mailOptions, function (error, body) {
		if (error) {
			console.log(errorCode, headers, error);
			callback(null, {
				errorCode,
				headers,
				body: JSON.stringify(error),
			});
		} else {
			console.log(successCode, headers, body);
			callback(null, {
				successCode,
				headers,
				body: JSON.stringify(body),
			});
		}
	});
}

Here’s an example log on the Netlify function console (apiKey replaced with [hidden]):

1:53:02 PM: 2021-10-27T20:53:02.388Z	undefined	ERROR	(node:8) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
1:53:02 PM: ae121626 INFO   c {
  username: 'api',
  apiKey: '[hidden]',
  publicApiKey: undefined,
  domain: 'mg.twelvenyc.com',
  auth: 'api:[hidden]',
  mute: false,
  timeout: undefined,
  host: 'api.mailgun.net',
  endpoint: '/v3',
  protocol: 'https:',
  port: 443,
  retry: 1,
  testMode: undefined,
  testModeLogger: undefined,
  options: {
    host: 'api.mailgun.net',
    endpoint: '/v3',
    protocol: 'https:',
    port: 443,
    auth: 'api:[hidden]',
    proxy: undefined,
    timeout: undefined,
    retry: 1,
    testMode: undefined,
    testModeLogger: undefined
  },
  mailgunTokens: {}
}
1:53:03 PM: ae121626 INFO   200 {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'Content-Type',
  'Access-Control-Allow-Credentials': 'true'
} {
  id: '<20211027205302.1.BEC72F2D158A93F1@mg.twelvenyc.com>',
  message: 'Queued. Thank you.'
}
1:53:03 PM: ae121626 Duration: 687.05 ms	Memory Usage: 67 MB	Init Duration: 306.59 ms

And here’s the error I’m seeing in the client side console log:

xhr.js:175 POST https://twelvenyc.com/.netlify/functions/send-email 502

createError.js:17 Uncaught (in promise) Error: Request failed with status code 502
    at e.exports (createError.js:17)
    at e.exports (settle.js:19)
    at XMLHttpRequest.m.onreadystatechange (xhr.js:65)

And just in case, here’s the front-end component that calls the function:

import React, { useState } from "react";
import cx from "classnames";
import { useForm } from "react-hook-form";
import { BlockContent } from "src/components/block-content";
import { ShadowBorder } from "src/components/shadow-border";

import * as styles from "./contact-form.module.css";

const endpoints = {
	contact: "https://twelvenyc.com/.netlify/functions/send-email",
};
const axios = require("axios");

const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

interface ContactFormProps {
	toEmail: string;
	bilingual: boolean;
	finePrint?: string;
	successMessage: any[];
}

export const ContactForm = ({
	toEmail = "hello@twelvenyc.com",
	bilingual = true,
	finePrint,
	successMessage,
}: ContactFormProps) => {
	const [success, setSuccess] = useState(false);

	const {
		register,
		handleSubmit,
		formState: { errors },
	} = useForm();

	const onSubmit = (data: any) => {
		// set to email
		data.toEmail = toEmail;

		// post to lambda function
		axios
			.post(endpoints.contact, JSON.stringify(data))
			.then((response: any) => {
				console.log(response);
				if (response.status !== 200) {
					handleError();
				} else {
					handleSuccess();
				}
			});
	};

	const handleSuccess = () => {
		// console.log("success!");
		setSuccess(true);
	};

	const handleError = () => {
		console.log("error!");
	};

	return (
		<div className={styles.contactForm}>
			{success ? (
				<BlockContent
					blocks={successMessage}
					className={cx("small-body", styles.successMessage)}
				/>
			) : (
				<form noValidate autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
					<div className={styles.row}>
						<div className={styles.inputContainer}>
							<label>
								<span className={cx("label", styles.inputLabel)}>
									Name{bilingual && <> / Nom</>}*
								</span>
								<input
									type="text"
									className={cx("small-body", errors.name ? styles.error : "")}
									placeholder={errors.fromEmail && "Required"}
									{...register("name", { required: true })}
								/>
							</label>
						</div>
						<div className={styles.inputContainer}>
							<label>
								<span className={cx("label", styles.inputLabel)}>
									Company Name{bilingual && <> / Nom de la société</>}*
								</span>
								<input
									type="text"
									className={cx(
										"small-body",
										errors.companyName ? styles.error : ""
									)}
									placeholder={errors.fromEmail && "Required"}
									{...register("companyName", { required: true })}
								/>
							</label>
						</div>
					</div>
					<div className={styles.row}>
						<div className={styles.inputContainer}>
							<label>
								<span className={cx("label", styles.inputLabel)}>Email*</span>
								<input
									type="email"
									className={cx(
										"small-body",
										errors.fromEmail ? styles.error : ""
									)}
									placeholder={errors.fromEmail && "Required"}
									{...register("fromEmail", {
										required: true,
										pattern: {
											value: EMAIL_REGEX,
											message: "Please enter a valid email address.",
										},
									})}
								/>
							</label>
						</div>
						<div className={styles.inputContainer}>
							<label>
								<span className={cx("label", styles.inputLabel)}>
									Phone Number{bilingual && <> / Téléphone</>}
								</span>
								<input
									className={cx("small-body")}
									{...register("phoneNumber")}
								/>
							</label>
						</div>
					</div>
					<div className={styles.row}>
						<div className={styles.inputContainer}>
							<label>
								<span className={cx("label", styles.inputLabel)}>
									Any additional info
									{bilingual && <> / Informations complémentaires</>}
								</span>
								<textarea
									className={cx("small-body")}
									{...register("message")}
								/>
							</label>
						</div>
					</div>
					<div className={styles.row}>
						<button type="submit" className={cx(styles.submitButton)}>
							<ShadowBorder className={styles.submitBorders}>
								<div className={styles.submitButtonInner}>
									<span className="label">Submit</span>
								</div>
							</ShadowBorder>
						</button>
					</div>
					{finePrint && (
						<p className={cx(styles.finePrint, "small-body")}>{finePrint}</p>
					)}
				</form>
			)}
		</div>
	);
};

Hi @blimpmason,

I’m slightly confused by what documentation you’re referring to. Could you please share some resources about the Mailgun library?

For example: GitHub - mailgun/mailgun.js: Javascript SDK for Mailgun seems to be the official library and it’s loading mailgun as mailgun.js and not mailgun-js.

Then, to send the email, the syntax seems to be mg.messages.create(): GitHub - mailgun/mailgun.js: Javascript SDK for Mailgun.

Also, another tip: exports.handler = async (event, context) following return statements is a recommended syntax as compared to the callback syntax that you’re using.

Thanks @hrishikesh — I’ll try switching to the official Mailgun library and replace the callback syntax with exports.handler and report back.

But what I don’t understand is that the lambda is working — all requests get processed by Mailgun successfully and a 200 status code is returned (per the Netlify function’s log). If everything is working on the function side, why would the browser report the 502 error?

One more note — this all worked perfectly as posted above for about 3 months and only just started throwing errors client side.

I refactored the lambda function using the official mailgun.js library, and now it’s working. I don’t really know why the original version stopped working but this is resolved for now!

Updated lambda function for reference:

/* eslint-disable no-console */
require("dotenv").config();
const formData = require("form-data");
const Mailgun = require("mailgun.js");
const mailgun = new Mailgun(formData);

const { MAILGUN_API_KEY, MAILGUN_DOMAIN } = process.env;

const mg = mailgun.client({
	username: "api",
	key: MAILGUN_API_KEY,
});

const sendEmail = async (data) => {
	console.log("Sending email");

	let { name, companyName, fromEmail, phoneNumber, message, toEmail } = data;
	let bodyText = `Name: ${name}
Company: ${companyName}
Email: ${fromEmail}
Phone Number: ${phoneNumber}

${message}`;

	let mailOptions = {
		from: `${name} <${fromEmail}>`,
		to: toEmail,
		replyTo: fromEmail,
		subject: "twelvenyc.com Contact Form Submission",
		text: `${bodyText}`,
	};

	const res = await mg.messages.create(MAILGUN_DOMAIN, mailOptions);

	return res;
};

export async function handler(event) {
	// Only allow POST
	if (event.httpMethod !== "POST") {
		return { statusCode: 405, body: "Method Not Allowed" };
	}

	try {
		const data = JSON.parse(event.body);
		await sendEmail(data);
		return {
			statusCode: 200,
			body: "Email sent!",
		};
	} catch (err) {
		return {
			statusCode: 500,
			body: err.message || err,
		};
	}
}

Maybe something changed on the library’s end? But glad that it’s working fine now.