winsoroaks
winsoroaks16mo ago

nodemailer + convex?

Hi team! sorry it's me again. im running into some bugs when i try to use nodemailer for my app.
# case 1
.
convex/
email/
├── react-email
│   └── templates
│   └── welcome.tsx
├── send-email.ts
└── transports
└── nodemailerApp.ts

# case 2
.
convex/
├── email
│   ├── react-email
│   │   └── templates
│   │   └── welcome.tsx
│   ├── send-email.ts
│   └── transports
│   └── nodemailerApp.ts
# case 1
.
convex/
email/
├── react-email
│   └── templates
│   └── welcome.tsx
├── send-email.ts
└── transports
└── nodemailerApp.ts

# case 2
.
convex/
├── email
│   ├── react-email
│   │   └── templates
│   │   └── welcome.tsx
│   ├── send-email.ts
│   └── transports
│   └── nodemailerApp.ts
in scenario 1, i've been running into
⠙ Preparing Convex functions...
✘ [ERROR] Could not resolve "child_process"

node_modules/nodemailer/lib/sendmail-transport/index.js:3:22:
3 │ const spawn = require('child_process').spawn;
╵ ~~~~~~~~~~~~~~~

The package "child_process" wasn't found on the file system but is built into node. Are you trying
to bundle for node? You can use "platform: 'node'" to do that, which will remove this error.
...
⠙ Preparing Convex functions...
✘ [ERROR] Could not resolve "child_process"

node_modules/nodemailer/lib/sendmail-transport/index.js:3:22:
3 │ const spawn = require('child_process').spawn;
╵ ~~~~~~~~~~~~~~~

The package "child_process" wasn't found on the file system but is built into node. Are you trying
to bundle for node? You can use "platform: 'node'" to do that, which will remove this error.
...
in scenario 2, i've been running into
⠋ Preparing Convex functions...
Unexpected Error: SyntaxError: 'from' expected. (17:49)
> 17 | import type * as email_react_email_node_modules_@babel_code_frame_LICENSE from "../email/react-email/node_modules/@babel/code-frame/LICENSE";
| ^
18 | import type * as email_react_email_node_modules_@babel_code_frame_lib_index from "../email/react-email/node_modules/@babel/code-frame/lib/index";
...
⠋ Preparing Convex functions...
Unexpected Error: SyntaxError: 'from' expected. (17:49)
> 17 | import type * as email_react_email_node_modules_@babel_code_frame_LICENSE from "../email/react-email/node_modules/@babel/code-frame/LICENSE";
| ^
18 | import type * as email_react_email_node_modules_@babel_code_frame_lib_index from "../email/react-email/node_modules/@babel/code-frame/lib/index";
...
does anyone know what did i do wrong? i also have "jsx": "react", set in my convex/tsconfig.ts. thanks! 🙏
18 Replies
ballingt
ballingt16mo ago
In scenario 2 it looks like you have a node_modules directory inside of the convex directory. You should delete that add these dependencies to the package.json outside of the convex directory. For scenario 1, it looks like you're trying to use nodemailer, which is a Node.js library. You can use Node.js libraries by adding "use node"; to the top of a file in the Convex directory, but you can only declare actions in that file.
winsoroaks
winsoroaksOP16mo ago
thanks! i will go with scenario 2. looks like im still getting scenario 1's error after deleting the node_modules. do i need to clear cache or something?
./convex
├── README.md
├── _generated
│   ├── api.d.ts
│   ├── api.js
│   ├── dataModel.d.ts
│   ├── server.d.ts
│   └── server.js
├── _schema.ts
├── auth.config.js
├── email
│   ├── sendEmail.ts
│   └── templates
│   └── welcome.tsx
├── tsconfig.json
└── vendorFuncs.ts
./convex
├── README.md
├── _generated
│   ├── api.d.ts
│   ├── api.js
│   ├── dataModel.d.ts
│   ├── server.d.ts
│   └── server.js
├── _schema.ts
├── auth.config.js
├── email
│   ├── sendEmail.ts
│   └── templates
│   └── welcome.tsx
├── tsconfig.json
└── vendorFuncs.ts
⠦ Preparing Convex functions...
✘ [ERROR] Could not resolve "url"

node_modules/nodemailer/lib/shared/index.js:5:23:
5 │ const urllib = require('url');
╵ ~~~~~

The package "url" wasn't found on the file system but is built into node. Are you trying to bundle
for node? You can use "platform: 'node'" to do that, which will remove this error.
⠦ Preparing Convex functions...
✘ [ERROR] Could not resolve "url"

node_modules/nodemailer/lib/shared/index.js:5:23:
5 │ const urllib = require('url');
╵ ~~~~~

The package "url" wasn't found on the file system but is built into node. Are you trying to bundle
for node? You can use "platform: 'node'" to do that, which will remove this error.
still this error for the dir above
// sendEmail.ts
...
if (process.env.NODE_ENV === "development") {
const html = render(emailTemplate)
const nodemailerAppTransport = nodemailer.createTransport({
host: "localhost",
port: 1025,
auth: {
user: process.env.NODEMAILER_LOCAL_USER,
pass: process.env.NODEMAILER_LOCAL_PASS,
},
})

return nodemailerAppTransport.sendMail({
...message,
html,
})
}

const resend = new Resend(process.env.RESEND_API_KEY)
return resend.emails.send({ ...message, react: emailTemplate })
// sendEmail.ts
...
if (process.env.NODE_ENV === "development") {
const html = render(emailTemplate)
const nodemailerAppTransport = nodemailer.createTransport({
host: "localhost",
port: 1025,
auth: {
user: process.env.NODEMAILER_LOCAL_USER,
pass: process.env.NODEMAILER_LOCAL_PASS,
},
})

return nodemailerAppTransport.sendMail({
...message,
html,
})
}

const resend = new Resend(process.env.RESEND_API_KEY)
return resend.emails.send({ ...message, react: emailTemplate })
ballingt
ballingt16mo ago
What does the top of sendEmail.ts look like, are you using "use node";? see https://docs.convex.dev/functions/actions#calling-third-party-apis-and-using-npm-packages, in order to use a Node.js package like this you'll need to add this line.
Actions | Convex Developer Hub
Actions can call third party services to do things such as processing a payment
winsoroaks
winsoroaksOP16mo ago
yea, i have that above. i didnt wrap an internalAction and just did it
"use node"

import { render } from "@react-email/render"
import { v } from "convex/values"
import nodemailer from "nodemailer"
import { Resend } from "resend"

import { internalAction } from "../_generated/server"

export const sendEmail = internalAction({
args: {
to: v.string(),
subject: v.string(),
emailTemplate: v.any(),
},
handler: async (ctx, { to, subject, emailTemplate }) => {
// const subject = "Welcome to Resend"
// const to = "hello@email.com"
// const emailTemplate = createElement(EmailTemplateResetPassword, {
// name: "",
// resetPasswordLink: "www.google.com",
// })

let message = {
from: "onboarding@resend.dev",
subject,
to,
}

if (process.env.NODE_ENV === "development") {
const html = render(emailTemplate)
const nodemailerAppTransport = nodemailer.createTransport({
host: "localhost",
port: 1025,
auth: {
user: process.env.NODEMAILER_LOCAL_USER,
pass: process.env.NODEMAILER_LOCAL_PASS,
},
})

return nodemailerAppTransport.sendMail({
...message,
html,
})
}

const resend = new Resend(process.env.RESEND_API_KEY)
return resend.emails.send({ ...message, react: emailTemplate })
},
})
"use node"

import { render } from "@react-email/render"
import { v } from "convex/values"
import nodemailer from "nodemailer"
import { Resend } from "resend"

import { internalAction } from "../_generated/server"

export const sendEmail = internalAction({
args: {
to: v.string(),
subject: v.string(),
emailTemplate: v.any(),
},
handler: async (ctx, { to, subject, emailTemplate }) => {
// const subject = "Welcome to Resend"
// const to = "hello@email.com"
// const emailTemplate = createElement(EmailTemplateResetPassword, {
// name: "",
// resetPasswordLink: "www.google.com",
// })

let message = {
from: "onboarding@resend.dev",
subject,
to,
}

if (process.env.NODE_ENV === "development") {
const html = render(emailTemplate)
const nodemailerAppTransport = nodemailer.createTransport({
host: "localhost",
port: 1025,
auth: {
user: process.env.NODEMAILER_LOCAL_USER,
pass: process.env.NODEMAILER_LOCAL_PASS,
},
})

return nodemailerAppTransport.sendMail({
...message,
html,
})
}

const resend = new Resend(process.env.RESEND_API_KEY)
return resend.emails.send({ ...message, react: emailTemplate })
},
})
calling it like that
export const myFunc = action({
args: {
id: v.id(DB),
},
handler: async (ctx, args) => {
await sendEmail(ctx, {
to: "hello@email.com",
subject: "hello",
emailTemplate: "hello",
})

await ctx.runMutation(internal.myFuncs.doFunc, {
id: args.id,
})
},
})
export const myFunc = action({
args: {
id: v.id(DB),
},
handler: async (ctx, args) => {
await sendEmail(ctx, {
to: "hello@email.com",
subject: "hello",
emailTemplate: "hello",
})

await ctx.runMutation(internal.myFuncs.doFunc, {
id: args.id,
})
},
})
is giving the node error doing
await ctx.runAction(internal.email.sendEmail, {
to: "hello@email.com",
subject: "hello",
emailTemplate: "hello",
})
await ctx.runAction(internal.email.sendEmail, {
to: "hello@email.com",
subject: "hello",
emailTemplate: "hello",
})
is giving me
Argument of type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is not assignable to parameter of type 'FunctionReference<"action", "internal" | "public">'.
Type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is missing the following properties from type 'FunctionReference<"action", "internal" | "public">': _type, _visibility, _args, _returnType
Argument of type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is not assignable to parameter of type 'FunctionReference<"action", "internal" | "public">'.
Type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is missing the following properties from type 'FunctionReference<"action", "internal" | "public">': _type, _visibility, _args, _returnType
ballingt
ballingt16mo ago
this type error is what you'd get if you use module as a function reference, e.g. internal.email instead of internal.email.sendEmail Right now you're calling the actions like sendEmail(ctx, ...) which means it's going to run in whatever environment myFunc is in. Does that file have "use node"; at the top? I'd suggest ctx.runAction(api.emails.sendEmail, {...}) instead when you just call the function it's as though it's not an action at all, it's just a helper function
winsoroaks
winsoroaksOP16mo ago
nope! i only have use node at the email/sendEmail.ts which means i should wrap sendEmail in action, add "use node" at the top of the file and from the myFunc, do ctx.runAction(api.email.sendEmail. {...})? should myFunc be wrapped in action?
ballingt
ballingt16mo ago
I would not add "use node" to the file containing myFunc and just sendEmail with the other syntax, but to back up a bit - "use node"; is a bundler directive; every module (file) in convex/ gets bundled separately, and if it has "use node"; at the top it will be bundled for Node.js instead of the Convex JS Runtime. - ctx.runAction() means "run that action as RPC;" this means the called function could be in a different runtime. Just calling a function, even if it's had a internalAction or action wrapper applied to it, is just a function call. It doesn't even have to be async if the function wasn't async.
winsoroaks
winsoroaksOP16mo ago
yes, that's what im doing i only have "use node" in the sendEmail.ts. myFunc is in a diff file from sendEmail.ts
ballingt
ballingt16mo ago
so I'd say use the RPC syntax (ctx.runAction(internal.email.sendEmail, ...)) and you should be set and generally I try to use Node.js only for what I have to, because it's a bit slower
winsoroaks
winsoroaksOP16mo ago
yea i think im only using it for nodemailer :/
ballingt
ballingt16mo ago
Another note, process.env.NODE_ENV === "development" doesn't work in Convex: it's always "development" for now. So better to set something manually in the Convex deployment dashboard with different values for dev and prod.
winsoroaks
winsoroaksOP16mo ago
ok i just did what you recommended
"use node"

import { render } from "@react-email/render"
import { v } from "convex/values"
import nodemailer from "nodemailer"
import { Resend } from "resend"

import { internalAction } from "../_generated/server"

export const sendEmail = internalAction({
args: {
to: v.string(),
subject: v.string(),
emailTemplate: v.any(),
},
handler: async (ctx, { to, subject, emailTemplate }) => {
"use node"

import { render } from "@react-email/render"
import { v } from "convex/values"
import nodemailer from "nodemailer"
import { Resend } from "resend"

import { internalAction } from "../_generated/server"

export const sendEmail = internalAction({
args: {
to: v.string(),
subject: v.string(),
emailTemplate: v.any(),
},
handler: async (ctx, { to, subject, emailTemplate }) => {
// myFunc.ts
ctx.runAction(internal.email.sendEmail, {
to: "hello",
subject: "hello",
emailTemplate: "hello",
})
// myFunc.ts
ctx.runAction(internal.email.sendEmail, {
to: "hello",
subject: "hello",
emailTemplate: "hello",
})
still getting
Argument of type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is not assignable to parameter of type 'FunctionReference<"action", "internal" | "public">'.
Type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is missing the following properties from type 'FunctionReference<"action", "internal" | "public">': _type, _visibility, _args, _returnType
Argument of type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is not assignable to parameter of type 'FunctionReference<"action", "internal" | "public">'.
Type '{ sendEmail: FunctionReference<"action", "internal", { to: string; subject: string; emailTemplate: any; }, SentMessageInfo | CreateEmailResponse>; }' is missing the following properties from type 'FunctionReference<"action", "internal" | "public">': _type, _visibility, _args, _returnType
there's no "use node" in myFunc.ts
ballingt
ballingt16mo ago
huh, what does replacing internal.email.sendEmail with internal.email.sendEmail.sendEmail do?
winsoroaks
winsoroaksOP16mo ago
lol this worked!!!
ballingt
ballingt16mo ago
that type error looks like there's an extra level of nesting Do you have a file called sendEmail or is there something weird going on
winsoroaks
winsoroaksOP16mo ago
ugh right! i forgot i have a file name called sendEmail... 🤦‍♂️ right... sorry i forgot i needed to camelCase filenames and confused myself lol
ballingt
ballingt16mo ago
np! One way to get rid of this level of nesting is to make the sendEmail function the default export, they you're allowed to drop it
winsoroaks
winsoroaksOP16mo ago
thanks so much tom! im all set 🙂

Did you find this page helpful?