@dennis, it took some finagling, but I was able to manually zip the file following the example you sent over. I can see that Netlify is attempting to upload the function but I still get the same failure:
12:22:58 PM: Build script success
12:22:58 PM: Starting to deploy site from 'public'
12:22:59 PM: Creating deploy tree asynchronously
12:23:00 PM: Creating deploy upload records
12:23:06 PM: 3 new files to upload
12:23:06 PM: 1 new functions to upload
12:25:56 PM: Failed to upload file: contact-form-submitted
12:25:56 PM: Failing build: Failed to deploy site
12:25:56 PM: Failed during stage 'deploying site': Failed to execute deploy: [PUT /deploys/{deploy_id}/functions/{name}][422] uploadDeployFunction default &{Code:0 Message:}
12:25:56 PM: Finished processing build request in 6m14.542724321s
I diff-checked the function with a different website where the same function uploads fine. The two files are identical.
Without any form of information on why this is failing to upload I’m left to complete guesses as to what’s causing the failure.
Is there any way to get more information on why the function won’t upload?
Here’s the function in its current form:
const queryString = require("query-string")
const human = require("humanparser")
const validator = require("validator")
const rp = require("request-promise")
const {
MAILJET_API_KEY,
MAILJET_SECRET_KEY,
FROM_EMAIL,
FROM_NAME,
TO_EMAIL,
TO_NAME,
} = process.env
const mailjet = require("node-mailjet").connect(
MAILJET_API_KEY,
MAILJET_SECRET_KEY
)
const validateFormName = (payload, errors) => {
if (!validator.equals(payload["form-name"], "Contact")) {
errors.push({ param: "form-name", msg: "Invalid Form" })
}
}
const validateFirstName = (payload, errors) => {
payload.firstName = validator.trim(payload.firstName)
if (validator.isEmpty(payload.firstName)) {
errors.push({ param: "firstName", msg: "First Name Required" })
}
if (!validator.isLength(payload.firstName, { min: 2 })) {
errors.push({ param: "firstName", msg: "Too Short" })
}
if (!validator.isLength(payload.firstName, { max: 50 })) {
errors.push({ param: "firstName", msg: "Too Long" })
}
payload.firstName = validator.escape(payload.firstName)
}
const validateLastName = (payload, errors) => {
payload.lastName = validator.trim(payload.lastName)
if (validator.isEmpty(payload.lastName)) {
errors.push({ param: "lastName", msg: "First Name Required" })
}
if (!validator.isLength(payload.lastName, { min: 2 })) {
errors.push({ param: "lastName", msg: "Too Short" })
}
if (!validator.isLength(payload.lastName, { max: 50 })) {
errors.push({ param: "lastName", msg: "Too Long" })
}
payload.lastName = validator.escape(payload.lastName)
}
const validateEmail = (payload, errors) => {
payload.email = validator.trim(payload.email)
if (validator.isEmpty(payload.email)) {
errors.push({ param: "email", msg: "Email Required" })
}
if (!validator.isEmail(payload.email)) {
errors.push({ param: "email", msg: "Must be a valid Email Address" })
}
payload.email = validator.normalizeEmail(payload.email)
}
const validateSubject = (payload, errors) => {
payload.subject = validator.trim(payload.subject)
if (validator.isEmpty(payload.subject)) {
errors.push({ param: "subject", msg: "Subject Required" })
}
if (!validator.isLength(payload.subject, { min: 2 })) {
errors.push({ param: "subject", msg: "Too Short" })
}
if (!validator.isLength(payload.subject, { max: 50 })) {
errors.push({ param: "subject", msg: "Too Long" })
}
payload.subject = validator.escape(payload.subject)
}
const validateMessage = (payload, errors) => {
payload.message = validator.trim(payload.message)
if (validator.isEmpty(payload.message)) {
errors.push({ param: "message", msg: "Message Required" })
}
if (!validator.isLength(payload.message, { min: 2 })) {
errors.push({ param: "message", msg: "Too Short" })
}
if (!validator.isLength(payload.message, { max: 2000 })) {
errors.push({ param: "message", msg: "Too Long" })
}
payload.message = validator.escape(payload.message)
}
const validateBotField = (payload, errors) => {
if (!validator.isEmpty(payload.botField)) {
errors.push({ param: "botField", msg: "Spam!" })
}
}
const validateRecaptcha = async (payload, errors) => {
if (validator.isEmpty(payload.recaptchaToken)) {
errors.push({
param: "recaptchaToken",
msg: "Recaptcha Error - Empty Token",
})
}
try {
const response = await rp(
`https://www.google.com/recaptcha/api/siteverify?secret=${process.env.SITE_RECAPTCHA_SECRET}&response=${payload.recaptchaToken}`
).json()
if (response.success === undefined || response.success !== true) {
errors.push({ param: "recaptchaToken", msg: "Recaptcha Error" })
}
} catch (e) {
return {
statusCode: 422,
body: JSON.stringify({ errors: e }),
}
}
}
exports.handler = async event => {
const payload = queryString.parse(event.body)
const errors = []
validateFormName(payload, errors)
validateFirstName(payload, errors)
validateLastName(payload, errors)
validateEmail(payload, errors)
validateSubject(payload, errors)
validateMessage(payload, errors)
validateBotField(payload, errors)
await validateRecaptcha(payload, errors)
const mapped = errors.reduce((mapping, error) => {
if (!mapping[error.param]) {
mapping[error.param] = error.msg
}
return mapping
}, {})
if (Object.getOwnPropertyNames(mapped).length !== 0) {
return {
statusCode: 422,
body: JSON.stringify({ errors: mapped }),
}
} else {
try {
await mailjet.post("send", { version: "v3.1" }).request({
"Messages": [
{
"From": {
"Email": FROM_EMAIL,
"Name": FROM_NAME,
},
"To": [
{
"Email": TO_EMAIL,
"Name": TO_NAME,
},
],
"ReplyTo": {
"Email": payload.email,
"Name": payload.firstName,
},
"Subject": `Inquiry: ${payload.firstName}`,
"TextPart": payload.message,
"HTMLPart": `
<h2>${payload.subject}</h2>
<h3>Details</h3>
<ul>
<li><strong>Name</strong>: ${payload.firstName}</li>
<li><strong>Email</strong>: ${payload.email}</li>
</ul>
<h3>Message</h3>
<p>${payload.message}</p>
`,
},
],
})
const fullName = `${payload.firstName} ${payload.lastName}`
return {
statusCode: 200,
body: JSON.stringify(human.parseName(fullName)),
}
} catch (err) {
console.log("error: ", err)
return {
statusCode: 400,
body: JSON.stringify({ error: err }),
}
}
}
}
Update: I tried all three approaches in the example (lambda/bundled-function, labda/zipped-function, and functions/zisi-function). I get the same failure on all three.