@@ -7,8 +7,20 @@ const corsHeaders = {
77 "Access-Control-Allow-Headers" : "Content-Type" ,
88} ;
99
10+ const ipStore : Record < string , { count : number ; firstRequestTime : number } > = { } ;
11+ const MAX_EMAILS_PER_HOUR = 3 ;
12+ const ONE_HOUR_MS = 60 * 60 * 1000 ;
13+
14+ setInterval ( ( ) => {
15+ const now = Date . now ( ) ;
16+ for ( const ip in ipStore ) {
17+ if ( now - ipStore [ ip ] . firstRequestTime > ONE_HOUR_MS ) {
18+ delete ipStore [ ip ] ;
19+ }
20+ }
21+ } , 10 * 60 * 1000 ) ;
22+
1023export const handler : Handler = async ( event ) => {
11- // CORS Preflight
1224 if ( event . httpMethod === "OPTIONS" ) {
1325 return {
1426 statusCode : 200 ,
@@ -17,7 +29,6 @@ export const handler: Handler = async (event) => {
1729 } ;
1830 }
1931
20- // Only allow POST
2132 if ( event . httpMethod !== "POST" ) {
2233 return {
2334 statusCode : 405 ,
@@ -26,29 +37,47 @@ export const handler: Handler = async (event) => {
2637 } ;
2738 }
2839
40+ const clientIP =
41+ event . headers [ "x-forwarded-for" ] ||
42+ event . headers [ "client-ip" ] ||
43+ event . headers [ "x-nf-client-connection-ip" ] ||
44+ "unknown" ;
45+
46+ const now = Date . now ( ) ;
47+
48+ if ( ! ipStore [ clientIP ] ) {
49+ ipStore [ clientIP ] = { count : 1 , firstRequestTime : now } ;
50+ } else {
51+ const elapsed = now - ipStore [ clientIP ] . firstRequestTime ;
52+
53+ if ( elapsed < ONE_HOUR_MS ) {
54+ if ( ipStore [ clientIP ] . count >= MAX_EMAILS_PER_HOUR ) {
55+ return {
56+ statusCode : 429 ,
57+ headers : corsHeaders ,
58+ body : JSON . stringify ( {
59+ error : "Rate limit exceeded: Max 3 emails per hour allowed." ,
60+ } ) ,
61+ } ;
62+ }
63+ ipStore [ clientIP ] . count ++ ;
64+ } else {
65+ ipStore [ clientIP ] = { count : 1 , firstRequestTime : now } ;
66+ }
67+ }
68+
2969 try {
3070 const { firstname, lastname, email, subject, message } = JSON . parse (
3171 event . body || "{}"
3272 ) ;
33- console . log ( "Parsed body:" , {
34- firstname,
35- lastname,
36- email,
37- subject,
38- message,
39- } ) ;
40- // Optionally validate required fields
73+
4174 if ( ! firstname || ! email || ! message ) {
4275 return {
4376 statusCode : 400 ,
4477 headers : corsHeaders ,
4578 body : JSON . stringify ( { error : "Missing required fields" } ) ,
4679 } ;
4780 }
48- // For debugging, log the event body and env vars
49- console . log ( "Request body:" , event . body ) ;
50- console . log ( "SMTP Host:" , process . env . SMTP_HOST ) ;
51- console . log ( "SMTP User:" , process . env . SMTP_USER ) ;
5281
5382 const transporter = nodemailer . createTransport ( {
5483 host : process . env . SMTP_HOST ,
@@ -65,7 +94,6 @@ export const handler: Handler = async (event) => {
6594 to : process . env . SMTP_TO ,
6695 subject : subject ,
6796 text : `${ message } \n${ email } ` ,
68- // html: `<p>this is message in html</p>`,
6997 } ) ;
7098
7199 return {
@@ -74,7 +102,6 @@ export const handler: Handler = async (event) => {
74102 body : JSON . stringify ( { message : "Email sent!" } ) ,
75103 } ;
76104 } catch ( error : any ) {
77- // Print error to log
78105 console . error ( "Error sending mail:" , error ) ;
79106 return {
80107 statusCode : 500 ,
0 commit comments