44 * Handles form submissions with:
55 * - Cloudflare Turnstile verification
66 * - Honeypot spam detection
7- * - Email sending via MailLayer
7+ * - Email sending via MailLayer templates
88 *
99 * Environment variables required:
1010 * - TURNSTILE_SECRET_KEY: Cloudflare Turnstile secret key
1111 * - MAILLAYER_API_KEY: MailLayer API key
12+ * - CONFIRMATION_TEMPLATE_ID: MailLayer template ID for user confirmation
13+ * - ADMIN_TEMPLATE_ID: MailLayer template ID for admin notification
1214 * - ADMIN_EMAIL: Email to receive submissions (default: hello@simplebytes.com)
1315 * - FROM_EMAIL: Email to send from (e.g., noreply@simplebytes.com)
16+ *
17+ * Template variables available:
18+ * - [name]: Sender's name
19+ * - [email]: Sender's email
20+ * - [project]: Selected project name
21+ * - [topic]: Selected topic
22+ * - [message]: Message content
1423 */
1524
1625const CORS_HEADERS = {
@@ -82,16 +91,28 @@ export default {
8291 // Send confirmation email to submitter
8392 await sendEmail ( env , {
8493 to : email ,
85- subject : `We received your message - Simple Bytes` ,
86- html : getConfirmationEmailHtml ( name , projectLabel , topicLabel , message ) ,
94+ templateId : env . CONFIRMATION_TEMPLATE_ID ,
95+ variables : {
96+ name,
97+ email,
98+ project : projectLabel ,
99+ topic : topicLabel ,
100+ message,
101+ } ,
87102 } ) ;
88103
89104 // Send notification to admin
90105 await sendEmail ( env , {
91106 to : env . ADMIN_EMAIL || 'hello@simplebytes.com' ,
92107 replyTo : email ,
93- subject : `[${ projectLabel } ] ${ topicLabel } from ${ name } ` ,
94- html : getAdminEmailHtml ( name , email , projectLabel , topicLabel , message ) ,
108+ templateId : env . ADMIN_TEMPLATE_ID ,
109+ variables : {
110+ name,
111+ email,
112+ project : projectLabel ,
113+ topic : topicLabel ,
114+ message,
115+ } ,
95116 } ) ;
96117
97118 return jsonResponse ( { success : true } ) ;
@@ -133,7 +154,7 @@ async function verifyTurnstile(token, secretKey) {
133154 return result . success === true ;
134155}
135156
136- async function sendEmail ( env , { to, replyTo, subject , html } ) {
157+ async function sendEmail ( env , { to, replyTo, templateId , variables } ) {
137158 const response = await fetch ( 'https://api.maillayer.com/v1/send' , {
138159 method : 'POST' ,
139160 headers : {
@@ -144,8 +165,8 @@ async function sendEmail(env, { to, replyTo, subject, html }) {
144165 from : env . FROM_EMAIL || 'noreply@simplebytes.com' ,
145166 to,
146167 reply_to : replyTo ,
147- subject ,
148- html ,
168+ template_id : templateId ,
169+ variables ,
149170 } ) ,
150171 } ) ;
151172
@@ -156,82 +177,3 @@ async function sendEmail(env, { to, replyTo, subject, html }) {
156177
157178 return response . json ( ) ;
158179}
159-
160- function getConfirmationEmailHtml ( name , project , topic , message ) {
161- return `
162- <!DOCTYPE html>
163- <html>
164- <head>
165- <meta charset="utf-8">
166- <meta name="viewport" content="width=device-width, initial-scale=1.0">
167- </head>
168- <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #171717; max-width: 600px; margin: 0 auto; padding: 20px;">
169- <div style="border-bottom: 1px solid #e5e5e5; padding-bottom: 20px; margin-bottom: 20px;">
170- <h1 style="font-size: 24px; font-weight: 600; margin: 0;">Simple Bytes</h1>
171- </div>
172-
173- <p>Hi ${ escapeHtml ( name ) } ,</p>
174-
175- <p>Thank you for reaching out! We've received your message and will get back to you as soon as possible.</p>
176-
177- <div style="background: #f5f5f5; border-radius: 8px; padding: 20px; margin: 20px 0;">
178- <p style="margin: 0 0 10px 0;"><strong>Project:</strong> ${ escapeHtml ( project ) } </p>
179- <p style="margin: 0 0 10px 0;"><strong>Topic:</strong> ${ escapeHtml ( topic ) } </p>
180- <p style="margin: 0 0 10px 0;"><strong>Your message:</strong></p>
181- <p style="margin: 0; white-space: pre-wrap;">${ escapeHtml ( message ) } </p>
182- </div>
183-
184- <p>Best regards,<br>The Simple Bytes Team</p>
185-
186- <div style="border-top: 1px solid #e5e5e5; padding-top: 20px; margin-top: 20px; font-size: 12px; color: #737373;">
187- <p style="margin: 0;">This is an automated confirmation. Please do not reply to this email.</p>
188- <p style="margin: 10px 0 0 0;"><a href="https://simplebytes.com" style="color: #525252;">simplebytes.com</a></p>
189- </div>
190- </body>
191- </html>
192- ` ;
193- }
194-
195- function getAdminEmailHtml ( name , email , project , topic , message ) {
196- return `
197- <!DOCTYPE html>
198- <html>
199- <head>
200- <meta charset="utf-8">
201- <meta name="viewport" content="width=device-width, initial-scale=1.0">
202- </head>
203- <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #171717; max-width: 600px; margin: 0 auto; padding: 20px;">
204- <div style="border-bottom: 1px solid #e5e5e5; padding-bottom: 20px; margin-bottom: 20px;">
205- <h1 style="font-size: 24px; font-weight: 600; margin: 0;">New Contact Form Submission</h1>
206- </div>
207-
208- <div style="background: #f5f5f5; border-radius: 8px; padding: 20px; margin-bottom: 20px;">
209- <p style="margin: 0 0 10px 0;"><strong>Name:</strong> ${ escapeHtml ( name ) } </p>
210- <p style="margin: 0 0 10px 0;"><strong>Email:</strong> <a href="mailto:${ escapeHtml ( email ) } ">${ escapeHtml ( email ) } </a></p>
211- <p style="margin: 0 0 10px 0;"><strong>Project:</strong> ${ escapeHtml ( project ) } </p>
212- <p style="margin: 0;"><strong>Topic:</strong> ${ escapeHtml ( topic ) } </p>
213- </div>
214-
215- <div style="background: #fafafa; border: 1px solid #e5e5e5; border-radius: 8px; padding: 20px;">
216- <p style="margin: 0 0 10px 0;"><strong>Message:</strong></p>
217- <p style="margin: 0; white-space: pre-wrap;">${ escapeHtml ( message ) } </p>
218- </div>
219-
220- <p style="margin-top: 20px; font-size: 12px; color: #737373;">
221- Reply directly to this email to respond to ${ escapeHtml ( name ) } .
222- </p>
223- </body>
224- </html>
225- ` ;
226- }
227-
228- function escapeHtml ( text ) {
229- const map = {
230- '&' : '&' ,
231- '<' : '<' ,
232- '>' : '>' ,
233- '"' : '"' ,
234- "'" : ''' ,
235- } ;
236- return String ( text ) . replace ( / [ & < > " ' ] / g, m => map [ m ] ) ;
237- }
0 commit comments