Skip to content

Commit

Permalink
Create contact form endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
pimterry committed Dec 12, 2024
1 parent 972c8a1 commit 150c7f9
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 0 deletions.
19 changes: 19 additions & 0 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/node": "^20.11.17",
"@types/node-fetch": "^2.5.2",
"@types/node-forge": "^1.3.1",
"@types/nodemailer": "^6.4.17",
"chai": "^4.2.0",
"destroyable-server": "^1.0.1",
"mocha": "^10.1.0",
Expand All @@ -51,6 +52,7 @@
"node-cache": "^5.1.2",
"node-fetch": "^2.6.1",
"node-forge": "^1.3.1",
"nodemailer": "^6.9.16",
"php-serialize": "^2.1.0",
"posthog-node": "^4.0.1",
"tsx": "^4.1.2"
Expand Down
104 changes: 104 additions & 0 deletions api/src/functions/contact-form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import nodemailer from 'nodemailer';

import { catchErrors, reportError } from '../errors';
import { delay } from '@httptoolkit/util';
import { getCorsResponseHeaders } from '../cors';

const {
CONTACT_FORM_DESTINATION,
SMTP_HOST,
SMTP_PORT,
SMTP_USERNAME,
SMTP_PASSWORD
} = process.env;

if (!CONTACT_FORM_DESTINATION) throw new Error('No contact form destination configured');

if (!SMTP_HOST) throw new Error('No SMTP host configured');
if (!SMTP_PORT) throw new Error('No SMTP port configured');
if (!SMTP_USERNAME) throw new Error('No SMTP user configured');
if (!SMTP_PASSWORD) throw new Error('No SMTP password configured');

const mailer = nodemailer.createTransport({
host: SMTP_HOST,
port: Number(SMTP_PORT),
secure: true,
auth: {
user: SMTP_USERNAME,
pass: SMTP_PASSWORD
}
});

const THANK_YOU_PAGE = 'https://httptoolkit.com/contact-thank-you/'

export const handler = catchErrors(async (event) => {
let headers = getCorsResponseHeaders(event);

if (event.httpMethod === 'OPTIONS') {
return { statusCode: 200, headers, body: '' };
} else if (event.httpMethod !== 'POST') {
return { statusCode: 405, headers, body: '' };
}

const formData = new URLSearchParams(event.body || '');
const {
name,
email,
message,
phone: honeypot
} = Object.fromEntries(formData);

if (honeypot) {
// We can remove this later - just reporting each hit for now to check if it's working
reportError('Contact form honeypot triggered', {
extraMetadata: { name, email, message, honeypot }
});

// Pretend it did actually work so they don't try again:
await delay(1000);
return {
statusCode: 302,
headers: {
Location: THANK_YOU_PAGE
},
body: ''
};
}

const fields = [
['Name', name],
['Email', email],
['Message', message]
]

fields.forEach(([field, value]) => {
if (!value) {
return {
statusCode: 400,
headers,
body: `${field} is required`
};
}
});

await mailer.sendMail({
from: 'Contact form <[email protected]>',
to: CONTACT_FORM_DESTINATION,
replyTo: email,
subject: 'HTTP Toolkit contact form message',
html: `<html><style>p { margin-bottom: 10px; }</style><body>
${
fields.map(([field, value]) => {
return `<p><strong>${field}</strong>:<br/>${value}</p>`;
}).join('')
}</body></html>`
});

return {
statusCode: 302,
headers: {
Location: THANK_YOU_PAGE
},
body: ''
};
});
3 changes: 3 additions & 0 deletions api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ apiRouter.options('*', (req, res) => {
'/auth/send-code',
'/auth/login',
'/auth/refresh-token',
'/contact-form',
'/update-team',
'/update-team-size',
'/cancel-subscription',
Expand Down Expand Up @@ -119,6 +120,8 @@ apiRouter.post('/auth/login',
);
apiRouter.post('/auth/refresh-token', rateLimit(RATE_LIMIT_PARAMS), lambdaWrapper('auth/refresh-token'));

apiRouter.post('/contact-form', lambdaWrapper('contact-form'));

apiRouter.post('/update-team', lambdaWrapper('update-team'));
apiRouter.post('/update-team-size', lambdaWrapper('update-team-size'));
apiRouter.post('/cancel-subscription', lambdaWrapper('cancel-subscription'));
Expand Down

0 comments on commit 150c7f9

Please sign in to comment.