Spaces:
Running
Running
Download src/utils/email.js from AndreaNotes1/auth-app-api: direct link, hf CLI and curl.
- Browser
- Download file 7.23 kB
-
https://huggingface.co/spaces/AndreaNotes1/auth-app-api/resolve/main/src/utils/email.js
- Command line
-
hf download hf://spaces/AndreaNotes1/auth-app-api/src/utils/email.js
-
curl -L -o email.js https://huggingface.co/spaces/AndreaNotes1/auth-app-api/resolve/main/src/utils/email.js
7.23 kB
| const nodemailer = require("nodemailer"); | |
| const { env } = require("../config/env"); | |
| const logger = require("./logger"); | |
| let transporter = null; | |
| function getTransporter() { | |
| if (transporter) return transporter; | |
| transporter = nodemailer.createTransport({ | |
| host: env.smtp.host, | |
| port: env.smtp.port, | |
| secure: env.smtp.secure, // true for 465, false for other ports (STARTTLS) | |
| auth: env.smtp.user ? { user: env.smtp.user, pass: env.smtp.pass } : undefined, | |
| requireTLS: !env.smtp.secure, // enforce STARTTLS when not already on a TLS port | |
| }); | |
| return transporter; | |
| } | |
| async function sendMail({ to, subject, html, text }) { | |
| try { | |
| const info = await getTransporter().sendMail({ | |
| from: env.emailFrom, | |
| to, | |
| subject, | |
| html, | |
| text, | |
| }); | |
| return info; | |
| } catch (err) { | |
| // Email failures must never crash a request or leak SMTP credentials. | |
| logger.error("Failed to send email", { subject, error: err.message }); | |
| return null; | |
| } | |
| } | |
| function baseLayout(bodyHtml) { | |
| return ` | |
| <div style="font-family:Arial,Helvetica,sans-serif;max-width:600px;margin:0 auto;color:#1a1a1a;"> | |
| <div style="padding:24px 0;border-bottom:2px solid #eee;"> | |
| <h2 style="margin:0;color:#111;">Aynwerd Academy</h2> | |
| </div> | |
| <div style="padding:24px 0;">${bodyHtml}</div> | |
| <div style="padding:16px 0;border-top:1px solid #eee;font-size:12px;color:#777;"> | |
| This is an automated message regarding your order. Never share your password or | |
| full payment reference by email. If you didn't make this request, contact | |
| support immediately. | |
| </div> | |
| </div>`; | |
| } | |
| async function sendOrderPlacedEmail({ to, name, order, courseTitle, bankDetails, payoneerDetails }) { | |
| const methodBlock = | |
| order.paymentMethod === "bank_wire" | |
| ? `<h3>Bank wire transfer instructions</h3> | |
| <table style="border-collapse:collapse;width:100%;font-size:14px;"> | |
| <tr><td style="padding:4px 0;color:#555;">Bank name</td><td><strong>${bankDetails.name}</strong></td></tr> | |
| <tr><td style="padding:4px 0;color:#555;">Account name</td><td><strong>${bankDetails.accountName}</strong></td></tr> | |
| <tr><td style="padding:4px 0;color:#555;">Account number</td><td><strong>${bankDetails.accountNumber}</strong></td></tr> | |
| <tr><td style="padding:4px 0;color:#555;">Routing number</td><td><strong>${bankDetails.routingNumber}</strong></td></tr> | |
| <tr><td style="padding:4px 0;color:#555;">SWIFT/BIC</td><td><strong>${bankDetails.swiftBic}</strong></td></tr> | |
| <tr><td style="padding:4px 0;color:#555;">Bank address</td><td><strong>${bankDetails.address}</strong></td></tr> | |
| </table> | |
| <p>Please use order number <strong>${order.orderNumber}</strong> as your payment reference.</p>` | |
| : `<h3>Payoneer payment instructions</h3> | |
| <p>Send payment to Payoneer account <strong>${payoneerDetails.accountEmail}</strong> | |
| or use your Payoneer app's "Request a Payment" link: <a href="${payoneerDetails.paymentUrl}">${payoneerDetails.paymentUrl}</a>.</p> | |
| <p>Please use order number <strong>${order.orderNumber}</strong> as your payment note.</p>`; | |
| const html = baseLayout(` | |
| <p>Hi ${name},</p> | |
| <p>Thanks for enrolling in <strong>${courseTitle}</strong>. Your order has been created and is | |
| awaiting payment${order.status === "awaiting_confirmation" ? " confirmation" : ""}.</p> | |
| <p><strong>Order number:</strong> ${order.orderNumber}<br/> | |
| <strong>Amount due:</strong> ${order.currency} ${order.amount.toFixed(2)}<br/> | |
| <strong>Payment method:</strong> ${order.paymentMethod === "bank_wire" ? "Bank wire transfer" : "Payoneer"}</p> | |
| ${methodBlock} | |
| <p>Once our team confirms your payment, you'll receive a separate email and full course access | |
| will be unlocked automatically on your account.</p> | |
| `); | |
| return sendMail({ | |
| to, | |
| subject: `Your Aynwerd order ${order.orderNumber} β payment instructions`, | |
| html, | |
| text: `Order ${order.orderNumber} created for ${courseTitle}. Amount due: ${order.currency} ${order.amount.toFixed(2)}.`, | |
| }); | |
| } | |
| async function sendPaymentConfirmedEmail({ to, name, order, courseTitle }) { | |
| const html = baseLayout(` | |
| <p>Hi ${name},</p> | |
| <p>Great news β we've confirmed your payment for <strong>${courseTitle}</strong>.</p> | |
| <p><strong>Order number:</strong> ${order.orderNumber}<br/> | |
| <strong>Amount paid:</strong> ${order.currency} ${order.amount.toFixed(2)}<br/> | |
| <strong>Confirmed on:</strong> ${new Date(order.confirmedAt).toUTCString()}</p> | |
| <p>Full access to the course has been unlocked on your account. Log in any time to get started.</p> | |
| `); | |
| return sendMail({ | |
| to, | |
| subject: `Payment confirmed β order ${order.orderNumber}`, | |
| html, | |
| text: `Payment confirmed for order ${order.orderNumber}. Course access unlocked.`, | |
| }); | |
| } | |
| async function sendVoucherRedeemedEmail({ to, name, order, courseTitle, voucherCode }) { | |
| const html = baseLayout(` | |
| <p>Hi ${name},</p> | |
| <p>Your voucher <strong>${voucherCode}</strong> has been redeemed for <strong>${courseTitle}</strong> β | |
| no payment was required.</p> | |
| <p><strong>Order number:</strong> ${order.orderNumber}</p> | |
| <p>Full access to the course has been unlocked on your account. Log in any time to get started.</p> | |
| `); | |
| return sendMail({ | |
| to, | |
| subject: `Voucher redeemed β ${courseTitle} unlocked`, | |
| html, | |
| text: `Voucher ${voucherCode} redeemed for ${courseTitle}. Course access unlocked. Order ${order.orderNumber}.`, | |
| }); | |
| } | |
| async function sendPaymentRejectedEmail({ to, name, order, courseTitle, reason }) { | |
| const html = baseLayout(` | |
| <p>Hi ${name},</p> | |
| <p>We were unable to confirm your payment for <strong>${courseTitle}</strong> (order | |
| <strong>${order.orderNumber}</strong>).</p> | |
| ${reason ? `<p><strong>Reason:</strong> ${reason}</p>` : ""} | |
| <p>Please reply to this email or contact support so we can help resolve this.</p> | |
| `); | |
| return sendMail({ | |
| to, | |
| subject: `Action needed on order ${order.orderNumber}`, | |
| html, | |
| text: `We could not confirm payment for order ${order.orderNumber}.${reason ? ` Reason: ${reason}` : ""}`, | |
| }); | |
| } | |
| async function sendAdminNewOrderAlert({ order, courseTitle, customerEmail }) { | |
| if (!env.adminNotifyEmail) return null; | |
| const html = baseLayout(` | |
| <p>New order awaiting confirmation.</p> | |
| <p><strong>Order:</strong> ${order.orderNumber}<br/> | |
| <strong>Customer:</strong> ${customerEmail}<br/> | |
| <strong>Course:</strong> ${courseTitle}<br/> | |
| <strong>Amount:</strong> ${order.currency} ${order.amount.toFixed(2)}<br/> | |
| <strong>Method:</strong> ${order.paymentMethod}</p> | |
| <p>Review and confirm it from the admin dashboard.</p> | |
| `); | |
| return sendMail({ | |
| to: env.adminNotifyEmail, | |
| subject: `New order ${order.orderNumber} awaiting confirmation`, | |
| html, | |
| }); | |
| } | |
| module.exports = { | |
| sendOrderPlacedEmail, | |
| sendPaymentConfirmedEmail, | |
| sendVoucherRedeemedEmail, | |
| sendPaymentRejectedEmail, | |
| sendAdminNewOrderAlert, | |
| }; | |