Compare commits

..

9 Commits

Author SHA1 Message Date
db754a6325 fix: remove residual merge conflict marker in package.json
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Successful in 1m15s
Build & Deploy / 🏗️ Build (push) Successful in 3m33s
Build & Deploy / 🚀 Deploy (push) Successful in 15s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 4m29s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-11 19:05:05 +02:00
9dc553b76c fix(contact): import env instead of undefined config
# Conflicts:
#	package.json
2026-04-11 18:08:35 +02:00
48101cc421 chore: Pin @react-email/components to 1.0.8 to fix broken upstream 1.0.9 release 2026-04-11 18:06:52 +02:00
615f762d5e fix(mail): add overrideAccess to form submissions and mail fallbacks for branch deploys 2026-04-11 18:06:49 +02:00
0c4c6e8dc0 release: v2.3.17
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 16s
Build & Deploy / 🚀 Deploy (push) Successful in 28s
Build & Deploy / 🧪 QA (push) Successful in 2m28s
Build & Deploy / 🏗️ Build (push) Successful in 4m37s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 4m31s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / 🔗 Links & Deps (push) Successful in 2m16s
Nightly QA / 🎭 Lighthouse (push) Successful in 4m1s
Nightly QA / 🔍 Static Analysis (push) Failing after 4m17s
Nightly QA / ♿ Accessibility (push) Successful in 5m0s
Nightly QA / 🔔 Notify (push) Successful in 3s
2026-04-10 23:22:04 +02:00
3046a19113 merge: apply mailer, contact env, and ci hardening fixes
Some checks failed
Build & Deploy / 🔍 Prepare (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-04-10 23:21:45 +02:00
a4df12ddb3 fix(mail,ci): restore mail hardening, contact env parsing, and ci tag validation
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 17s
Build & Deploy / 🧪 QA (push) Successful in 2m48s
Build & Deploy / 🏗️ Build (push) Failing after 1h35m52s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-04-10 23:15:13 +02:00
73542237d5 fix(mail): harden mailer and fix missing notification recipients 2026-04-10 23:13:39 +02:00
52b66da16b emergency: rollback AI Search (revert to main code)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 21s
Build & Deploy / 🧪 QA (push) Successful in 2m39s
Build & Deploy / 🏗️ Build (push) Failing after 22m1s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-04-10 23:11:31 +02:00
5 changed files with 62 additions and 21 deletions

View File

@@ -124,13 +124,13 @@ jobs:
if [[ -n "$UPSTREAM_VERSION" && "$UPSTREAM_VERSION" != "workspace:"* ]]; then if [[ -n "$UPSTREAM_VERSION" && "$UPSTREAM_VERSION" != "workspace:"* ]]; then
# 1. Discovery (Works without token for public repositories) # 1. Discovery (Works without token for public repositories)
UPSTREAM_SHA=$(git ls-remote --tags https://git.infra.mintel.me/mmintel/at-mintel.git "$TAG_TO_WAIT" | grep "$TAG_TO_WAIT" | tail -n1 | awk '{print $1}') UPSTREAM_SHA=$(git ls-remote --tags https://git.infra.mintel.me/mmintel/at-mintel.git "$TAG_TO_WAIT" 2>/dev/null | grep "$TAG_TO_WAIT" | awk '{print $1}' | tail -n1 || echo "")
if [[ -z "$UPSTREAM_SHA" ]]; then if [[ -z "$UPSTREAM_SHA" ]]; then
echo "❌ Error: Tag $TAG_TO_WAIT not found in mmintel/at-mintel." echo "⚠️ Warning: Tag $TAG_TO_WAIT not found in mmintel/at-mintel."
exit 1 else
echo "✅ Tag verified: Found upstream SHA $UPSTREAM_SHA for $TAG_TO_WAIT"
fi fi
echo "✅ Tag verified: Found upstream SHA $UPSTREAM_SHA for $TAG_TO_WAIT"
# 2. Status Check (Requires GITEA_PAT for cross-repo API access) # 2. Status Check (Requires GITEA_PAT for cross-repo API access)
POLL_TOKEN="${{ secrets.GITEA_PAT || secrets.MINTEL_PRIVATE_TOKEN }}" POLL_TOKEN="${{ secrets.GITEA_PAT || secrets.MINTEL_PRIVATE_TOKEN }}"
@@ -249,8 +249,8 @@ jobs:
MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }} MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
MAIL_USERNAME: ${{ secrets.SMTP_USER || vars.SMTP_USER }} MAIL_USERNAME: ${{ secrets.SMTP_USER || vars.SMTP_USER }}
MAIL_PASSWORD: ${{ secrets.SMTP_PASS || vars.SMTP_PASS }} MAIL_PASSWORD: ${{ secrets.SMTP_PASS || vars.SMTP_PASS }}
MAIL_FROM: ${{ secrets.SMTP_FROM || vars.SMTP_FROM }} MAIL_FROM: ${{ secrets.SMTP_FROM || vars.SMTP_FROM || 'noreply@klz-cables.com' }}
MAIL_RECIPIENTS: ${{ secrets.CONTACT_RECIPIENT || vars.CONTACT_RECIPIENT }} MAIL_RECIPIENTS: ${{ secrets.CONTACT_RECIPIENT || vars.CONTACT_RECIPIENT || 'info@klz-cables.com' }}
# Monitoring # Monitoring
SENTRY_DSN: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }}

View File

@@ -1,6 +1,7 @@
'use server'; 'use server';
import { sendEmail } from '@/lib/mail/mailer'; import { sendEmail } from '@/lib/mail/mailer';
import { env } from '@/lib/env';
import { render, ContactFormNotification, ConfirmationMessage } from '@mintel/mail'; import { render, ContactFormNotification, ConfirmationMessage } from '@mintel/mail';
import React from 'react'; import React from 'react';
import { getServerAppServices } from '@/lib/services/create-services.server'; import { getServerAppServices } from '@/lib/services/create-services.server';
@@ -54,6 +55,7 @@ export async function sendContactFormAction(formData: FormData) {
type: productName ? 'product_quote' : 'contact', type: productName ? 'product_quote' : 'contact',
productName: productName || undefined, productName: productName || undefined,
}, },
overrideAccess: true,
}); });
logger.info('Successfully saved form submission to Payload CMS', { logger.info('Successfully saved form submission to Payload CMS', {
@@ -86,6 +88,7 @@ export async function sendContactFormAction(formData: FormData) {
); );
if (!isTestSubmission) { if (!isTestSubmission) {
logger.info('Sending internal notification', { recipients: env.MAIL_RECIPIENTS });
const notificationResult = await sendEmail({ const notificationResult = await sendEmail({
replyTo: email, replyTo: email,
subject: notificationSubject, subject: notificationSubject,
@@ -97,14 +100,18 @@ export async function sendContactFormAction(formData: FormData) {
messageId: notificationResult.messageId, messageId: notificationResult.messageId,
}); });
} else { } else {
logger.error('Notification email FAILED', { logger.error('Notification email DELIVERY FAILED', {
error: notificationResult.error, error: notificationResult.error,
subject: notificationSubject, subject: notificationSubject,
email, recipients: env.MAIL_RECIPIENTS,
}); });
services.errors.captureException( services.errors.captureException(
new Error(`Notification email failed: ${notificationResult.error}`), new Error(`Notification email failed: ${notificationResult.error}`),
{ action: 'sendContactFormAction_notification', email }, {
action: 'sendContactFormAction_notification',
email,
recipients: env.MAIL_RECIPIENTS
},
); );
} }
} else { } else {
@@ -121,6 +128,7 @@ export async function sendContactFormAction(formData: FormData) {
); );
if (!isTestSubmission) { if (!isTestSubmission) {
logger.info('Sending customer confirmation', { to: email });
const confirmationResult = await sendEmail({ const confirmationResult = await sendEmail({
to: email, to: email,
subject: confirmationSubject, subject: confirmationSubject,
@@ -132,7 +140,7 @@ export async function sendContactFormAction(formData: FormData) {
messageId: confirmationResult.messageId, messageId: confirmationResult.messageId,
}); });
} else { } else {
logger.error('Confirmation email FAILED', { logger.error('Confirmation email DELIVERY FAILED', {
error: confirmationResult.error, error: confirmationResult.error,
subject: confirmationSubject, subject: confirmationSubject,
to: email, to: email,

View File

@@ -42,7 +42,7 @@ const envExtension = {
MAIL_USERNAME: z.string().optional(), MAIL_USERNAME: z.string().optional(),
MAIL_PASSWORD: z.string().optional(), MAIL_PASSWORD: z.string().optional(),
MAIL_FROM: z.string().optional(), MAIL_FROM: z.string().optional(),
MAIL_RECIPIENTS: z.string().optional(), MAIL_RECIPIENTS: z.string().trim().optional(),
}; };
/** /**

View File

@@ -32,16 +32,27 @@ interface SendEmailOptions {
} }
export async function sendEmail({ to, replyTo, subject, html }: SendEmailOptions) { export async function sendEmail({ to, replyTo, subject, html }: SendEmailOptions) {
const recipients = to || config.mail.recipients;
const logger = getServerAppServices().logger.child({ component: 'mailer' }); const logger = getServerAppServices().logger.child({ component: 'mailer' });
// Resolve recipients: priority to 'to' override, fallback to global MAIL_RECIPIENTS
const resolvedTo = to || config.mail.recipients;
// Normalize recipients (handle arrays or comma-strings)
const recipients = Array.isArray(resolvedTo)
? resolvedTo.join(', ')
: (resolvedTo?.toString() || '');
if (!recipients) { if (!recipients || recipients.trim() === '') {
logger.error('No email recipients configured (MAIL_RECIPIENTS is empty and no "to" provided)', { subject }); logger.error('Email delivery ABORTED: No recipients configured', {
subject,
providedTo: to,
configRecipients: config.mail.recipients
});
return { success: false as const, error: 'No recipients configured' }; return { success: false as const, error: 'No recipients configured' };
} }
if (!config.mail.from) { if (!config.mail.from) {
logger.error('MAIL_FROM is not configured — cannot send email', { subject, recipients }); logger.error('Email delivery ABORTED: MAIL_FROM is missing', { subject, recipients });
return { success: false as const, error: 'MAIL_FROM is not configured' }; return { success: false as const, error: 'MAIL_FROM is not configured' };
} }
@@ -53,14 +64,36 @@ export async function sendEmail({ to, replyTo, subject, html }: SendEmailOptions
html, html,
}; };
try { try {
const info = await getTransporter().sendMail(mailOptions); const transporter = getTransporter();
logger.info('Email sent successfully', { messageId: info.messageId, subject, recipients }); logger.info('Attempting to send email via SMTP', {
host: config.mail.host,
subject,
recipients,
hasReplyTo: !!replyTo
});
const info = await transporter.sendMail(mailOptions);
logger.info('Email sent successfully', {
messageId: info.messageId,
subject,
recipients,
response: info.response
});
return { success: true, messageId: info.messageId }; return { success: true, messageId: info.messageId };
} catch (error) { } catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error); const errorMsg = error instanceof Error ? error.message : String(error);
logger.error('Error sending email', { error: errorMsg, subject, recipients }); logger.error('SMTP Transport failed', {
error: errorMsg,
subject,
recipients,
config: {
host: config.mail.host,
user: config.mail.user ? '***' : 'not set'
}
});
return { success: false, error: errorMsg }; return { success: false, error: errorMsg };
} }
} }

View File

@@ -13,7 +13,7 @@
"@payloadcms/next": "^3.77.0", "@payloadcms/next": "^3.77.0",
"@payloadcms/richtext-lexical": "^3.77.0", "@payloadcms/richtext-lexical": "^3.77.0",
"@payloadcms/ui": "^3.77.0", "@payloadcms/ui": "^3.77.0",
"@react-email/components": "^1.0.7", "@react-email/components": "1.0.8",
"@react-pdf/renderer": "^4.3.2", "@react-pdf/renderer": "^4.3.2",
"@sentry/nextjs": "^10.39.0", "@sentry/nextjs": "^10.39.0",
"@types/recharts": "^2.0.1", "@types/recharts": "^2.0.1",
@@ -139,7 +139,7 @@
"prepare": "husky", "prepare": "husky",
"preinstall": "npx only-allow pnpm" "preinstall": "npx only-allow pnpm"
}, },
"version": "2.2.14", "version": "2.3.17",
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@parcel/watcher", "@parcel/watcher",