server.js (12384B)
1 const express = require('express'); 2 const { Resend } = require('resend'); 3 const { Webhook } = require('svix'); 4 5 const app = express(); 6 const port = process.env.PORT || 3000; 7 8 const resend = new Resend(process.env.RESEND_API_KEY); 9 const webhookSecret = process.env.RESEND_WEBHOOK_SECRET; 10 11 app.post('/api/incoming', express.text({ type: 'application/json' }), async (req, res) => { 12 try { 13 const rawBody = req.body; 14 const headers = { 15 'svix-id': req.headers['svix-id'], 16 'svix-timestamp': req.headers['svix-timestamp'], 17 'svix-signature': req.headers['svix-signature'], 18 }; 19 20 const wh = new Webhook(webhookSecret); 21 let event; 22 23 try { 24 event = wh.verify(rawBody, headers); 25 } catch (err) { 26 console.error(err.message); 27 return res.status(400).json({ error: 'Invalid signature' }); 28 } 29 30 if (event.type !== 'email.received') { 31 return res.status(200).json({ message: 'Not an email event' }); 32 } 33 34 const emailData = event.data; 35 const originalSender = emailData.from; 36 const originalRecipient = emailData.to[0].toLowerCase(); 37 const subject = emailData.subject || 'No Subject'; 38 39 const allowedString = process.env.ALLOWED_ALIASES || ''; 40 const allowedAliases = allowedString.split(',').map(alias => alias.trim().toLowerCase()); 41 42 if (!allowedAliases.includes(originalRecipient)) { 43 return res.status(200).json({ success: true, message: 'Alias ignored' }); 44 } 45 46 const personalGmail = process.env.PERSONAL_GMAIL; 47 const forwardingAddress = process.env.FORWARDING_BOT_ADDRESS; 48 49 // Premium Adaptive Google-style HTML Template 50 const notificationHtml = ` 51 <!DOCTYPE html> 52 <html lang="en" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"> 53 <head> 54 <meta charset="UTF-8"> 55 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 56 <meta name="color-scheme" content="light dark"> 57 <meta name="supported-color-schemes" content="light dark"> 58 <title>New Email Notification</title> 59 60 <style> 61 @import url('https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500&family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;500&display=swap'); 62 63 body { 64 margin: 0; 65 padding: 0; 66 -webkit-font-smoothing: antialiased; 67 -moz-osx-font-smoothing: grayscale; 68 } 69 70 .btn-pill:hover { 71 opacity: 0.9; 72 transform: translateY(-1px); 73 transition: all 0.2s ease; 74 } 75 76 @media (prefers-color-scheme: dark) { 77 .bg-outer { background-color: #131314 !important; } 78 .bg-card { background-color: #1E1F20 !important; } 79 .bg-tint { background-color: #282A2C !important; } 80 .text-primary { color: #E3E3E3 !important; } 81 .text-secondary { color: #C4C7C5 !important; } 82 .text-accent { color: #A8C7FA !important; } 83 .border-card { border-color: #444746 !important; } 84 .divider { background-color: #444746 !important; } 85 .btn-pill { 86 background-color: #A8C7FA !important; 87 color: #000000 !important; 88 } 89 .icon-fill { fill: #8E918F !important; } 90 } 91 92 /* Gmail Dark Mode Fixes */ 93 [data-ogsb] .bg-outer { background-color: #131314 !important; } 94 [data-ogsb] .bg-card { background-color: #1E1F20 !important; } 95 [data-ogsb] .bg-tint { background-color: #282A2C !important; } 96 [data-ogsc] .text-primary { color: #E3E3E3 !important; } 97 [data-ogsc] .text-secondary { color: #C4C7C5 !important; } 98 [data-ogsc] .text-accent { color: #A8C7FA !important; } 99 [data-ogsb] .border-card { border-color: #444746 !important; } 100 [data-ogsb] .divider { background-color: #444746 !important; } 101 [data-ogsb] .btn-pill { background-color: #A8C7FA !important; } 102 [data-ogsc] .btn-pill { color: #000000 !important; } 103 [data-ogsc] .icon-fill { fill: #8E918F !important; } 104 </style> 105 </head> 106 107 <body class="bg-outer" style="margin: 0; padding: 0; background-color: #F8F9FA; font-family: 'Google Sans', Roboto, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased;"> 108 109 <table width="100%" border="0" cellspacing="0" cellpadding="0" class="bg-outer" style="background-color: #F8F9FA; width: 100%; height: 100%;"> 110 <tr> 111 <td align="center" style="padding: 40px 16px;"> 112 113 <table border="0" cellspacing="0" cellpadding="0" class="bg-card border-card" style="background-color: #FFFFFF; border-radius: 28px; max-width: 600px; width: 100%; overflow: hidden; border: 1px solid #C4C7C5;"> 114 115 <tr> 116 <td style="padding: 40px 32px 16px 32px;"> 117 <h2 class="text-primary" style="margin: 0; font-family: 'Google Sans', Roboto, sans-serif; font-size: 22px; font-weight: 500; color: #1F1F1F; letter-spacing: -0.5px;">Incoming Message</h2> 118 <p class="text-secondary" style="margin: 8px 0 0 0; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-weight: 400; color: #444746; line-height: 1.5;">A new email has been routed to your developer inbox.</p> 119 </td> 120 </tr> 121 122 <tr> 123 <td style="padding: 0 32px;"> 124 <div class="divider" style="height: 1px; background-color: #E3E3E3; width: 100%; margin: 16px 0;"></div> 125 </td> 126 </tr> 127 128 <tr> 129 <td style="padding: 16px 32px;"> 130 131 <div style="margin-bottom: 24px;"> 132 <span class="text-accent" style="display: block; font-family: Roboto, Arial, sans-serif; font-size: 11px; font-weight: 500; color: #0B57D0; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px;">Sender Email ID</span> 133 <span class="text-primary" style="display: block; font-family: Roboto, Arial, sans-serif; font-size: 16px; font-weight: 400; color: #1F1F1F; word-break: break-all; line-height: 1.4;">${originalSender}</span> 134 </div> 135 136 <div style="margin-bottom: 24px;"> 137 <span class="text-accent" style="display: block; font-family: Roboto, Arial, sans-serif; font-size: 11px; font-weight: 500; color: #0B57D0; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px;">Subject</span> 138 <span class="text-primary" style="display: block; font-family: 'Google Sans', Roboto, Arial, sans-serif; font-size: 18px; font-weight: 500; color: #1F1F1F; line-height: 1.4;">${subject}</span> 139 </div> 140 141 <div style="margin-bottom: 32px;"> 142 <span class="text-accent" style="display: block; font-family: Roboto, Arial, sans-serif; font-size: 11px; font-weight: 500; color: #0B57D0; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px;">Email Tracking ID</span> 143 144 <table border="0" cellspacing="0" cellpadding="0" style="width: 100%;"> 145 <tr> 146 <td class="bg-tint" style="background-color: #F0F4F9; border-radius: 8px; padding: 12px 16px;"> 147 <span class="text-secondary" style="display: block; font-family: 'Roboto Mono', monospace; font-size: 14px; font-weight: 500; color: #444746; word-break: break-all;"> 148 ${emailData.email_id || 'N/A'} 149 </span> 150 </td> 151 </tr> 152 </table> 153 </div> 154 155 </td> 156 </tr> 157 158 <tr> 159 <td style="padding: 8px 32px 40px 32px;"> 160 <table border="0" cellspacing="0" cellpadding="0"> 161 <tr> 162 <td align="center" style="border-radius: 100px;"> 163 <a href="https://resend.com/emails/${emailData.email_id}" target="_blank" class="btn-pill" style="display: inline-block; padding: 12px 32px; background-color: #0B57D0; font-family: 'Google Sans', Roboto, Arial, sans-serif; font-size: 14px; font-weight: 500; color: #FFFFFF; text-decoration: none; border-radius: 100px;"> 164 Open Resend Dashboard 165 </a> 166 </td> 167 </tr> 168 </table> 169 </td> 170 </tr> 171 172 </table> 173 174 <table border="0" cellspacing="0" cellpadding="0" style="max-width: 600px; width: 100%; margin-top: 32px;"> 175 <tr> 176 <td align="center" style="padding-bottom: 24px;"> 177 <p class="text-secondary" style="margin: 0; font-family: Roboto, Arial, sans-serif; font-size: 12px; color: #747775; line-height: 1.6;">Automated alert by Notifier Bot.<br>Please do not reply to this unmonitored email.</p> 178 </td> 179 </tr> 180 181 <tr> 182 <td align="center" style="padding-bottom: 16px;"> 183 <table border="0" cellspacing="0" cellpadding="0"> 184 <tr> 185 <td style="padding: 0 12px;"> 186 <a href="https://amit.is-a.dev" target="_blank" style="text-decoration: none;" title="Website"> 187 <svg width="20" height="20" viewBox="0 0 24 24" fill="#747775" class="icon-fill" xmlns="http://www.w3.org/2000/svg" style="display: block; border: 0;"> 188 <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/> 189 </svg> 190 </a> 191 </td> 192 <td style="padding: 0 12px;"> 193 <a href="https://github.com/notamitgamer" target="_blank" style="text-decoration: none;" title="GitHub"> 194 <svg width="20" height="20" viewBox="0 0 16 16" fill="#747775" class="icon-fill" xmlns="http://www.w3.org/2000/svg" style="display: block; border: 0;"> 195 <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/> 196 </svg> 197 </a> 198 </td> 199 <td style="padding: 0 12px;"> 200 <a href="mailto:amitdutta4255@gmail.com" style="text-decoration: none;" title="Email"> 201 <svg width="20" height="20" viewBox="0 0 24 24" fill="#747775" class="icon-fill" xmlns="http://www.w3.org/2000/svg" style="display: block; border: 0;"> 202 <path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/> 203 </svg> 204 </a> 205 </td> 206 </tr> 207 </table> 208 </td> 209 </tr> 210 211 <tr> 212 <td align="center"> 213 <p class="text-secondary" style="margin: 0; font-family: Roboto, Arial, sans-serif; font-size: 12px; color: #747775;"> 214 © 2025-2026 Amit Dutta • <a href="https://amit.is-a.dev" class="text-secondary" style="color: #747775; text-decoration: none;">amit.is-a.dev</a> 215 </p> 216 </td> 217 </tr> 218 </table> 219 220 </td> 221 </tr> 222 </table> 223 224 </body> 225 </html> 226 `; 227 228 const payload = { 229 from: `Notifier Bot <${forwardingAddress}>`, 230 to: [personalGmail], 231 reply_to: originalSender, 232 subject: subject, 233 html: notificationHtml 234 }; 235 236 const { data, error } = await resend.emails.send(payload); 237 238 if (error) { 239 console.error(error); 240 return res.status(500).json({ error: error.message }); 241 } 242 243 return res.status(200).json({ success: true, id: data?.id }); 244 245 } catch (error) { 246 console.error(error); 247 return res.status(500).json({ error: 'Internal Server Error' }); 248 } 249 }); 250 251 app.get('/ping', (req, res) => { 252 res.status(200).send('Server is awake!'); 253 }); 254 255 app.listen(port, () => { 256 console.log(`Email router listening on port ${port}`); 257 });