So I wrote out this long post and ended up solving the issue myself. I figured I would post it in case it helps anyone.
I have a utility function that runs on the server to grab an email template when invoked. Iβm creating the path as described by the documentation
const publicFolderPath = path.join(process.cwd(), 'src', 'utils',
emailTemplates/${template}.html);
This works fine on my local machine both in dev and running build / start. When I deploy to vercel I get the error:
ENOENT: no such file or directory, open β/var/task/src/utils/emailTemplates/inquiry-information.htmlβ
my current directory structure looks like this:
PROJECT/
βββ .next/
βββ node_modules/
βββ public/
βββ src/
β βββ app/
β β βββ about/
β β βββ contact/
β β β βββ contact.module.css
β β β βββ page.tsx
β β βββ customers/
β β βββ privacy-policy/
β β βββ terms-of-service/
β β βββ actions.tsx
β β βββ apple-icon.png
β β βββ favicon.ico
β β βββ globals.css
β β βββ icon.png
β β βββ icon2.png
β β βββ icon3.png
β β βββ layout.tsx
β β βββ manifest.ts
β β βββ page.module.css
β β βββ page.tsx
β βββ components/
β βββ data/
β βββ types/
β βββ utils/
β βββ emailTemplates/
β βββ inquiry-information.html
β βββ inquiry.html
β βββ createEmail.tsx
β βββ emailTransporter.tsx
β βββ toast.ts
βββ .env.local
βββ .env.production
βββ .eslintrc.json
βββ .gitignore
βββ next-env.d.ts
βββ next.config.mjs
βββ package-lock.json
βββ package.json
βββ README.md
βββ tsconfig.json
Iβve also tried putting the templates in the public folder and Iβve had the same issues. Hereβs my createEmail file as well:
// node utils
import { promises as fs } from 'fs'; // Use fs to read the file asynchronously
import path from 'path';
// nodemailer
import transporter from './emailTransporter';
// types
import { EmailData } from '@/types/email';
const sendEmail = async (to: string, subject: string, template: string, data: EmailData) => {
try {
const publicFolderPath = path.join(process.cwd(), 'src', 'utils', `emailTemplates/${template}.html`);
console.log('public path', publicFolderPath);
console.log('dirname', __dirname);
console.log('next path', process.cwd());
// Read the HTML file
let htmlTemplate = await fs.readFile(publicFolderPath, 'utf-8');
// loop through the data object and replace what exists
for (const [key, value] of Object.entries(data)) {
htmlTemplate = htmlTemplate.replaceAll(`{{${key}}}`, String(value));
}
const mailOptions = {
from: 'privateInfo',
to: to,
subject: subject,
html: htmlTemplate,
};
const email = await transporter.sendMail(mailOptions);
console.log('Email information:', email.messageId);
} catch (error) {
if (error instanceof Error) {
console.error('Failed to send email:', error.message);
} else {
console.error('An unknown error occurred while sending email');
}
// throw this error to stop the second email from attempting to send
throw new Error('<Error occurred in createEmail function>');
}
};
export default sendEmail;
The fix ended up being needing to add this to the next config file:
experimental: {
outputFileTracingIncludes: {
'/**': ['./src/utils/emailTemplates/**/*'],
},
},
I suspect it has to do with the build treeshaking the imports and not including files that arenβt explicitly included or linked to in something like an img tag. However that wouldnβt explain how the build works locally. If anyone knows exactly why this fix works and if thereβs a better way, or if thereβs a way to optimize this option please let me know.