Objects are not valid as a React child Error when sending react-email with resend
Describe the Bug
Sending an email with resend using react component throws a following error:
[Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.]
It used to work in the past, but some updates (react-email, resend or react) must have broken it.
If sent as a HTML string, it works.
Which package is affected (leave empty if unsure)
react-email
Link to the code that reproduces this issue
private repo
To Reproduce
Sending an email with resend using react component:
await resend.emails.send({
to: email,
from: 'Page <[email protected]>',
subject: 'Verify your account',
react: VerifyLinkEmail({
token,
url,
}),
})
throws a following error:
[Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.]
The E-Mail is this:
const VerifyLinkEmail = ({ token, url }: VerifyLinkEmailProps) => {
return (
<Html>
<Head />
<Preview>Confirm the e-mail to register.</Preview>
<Tailwind>
<Body className="my-12 bg-white font-sans">
<Container className="mx-auto w-[400px] border px-4 py-10 text-center shadow-md">
<Section className="my-12">
<Img
src="https://link/logo_plain.png"
className="mx-auto h-16 w-16"
/>
<Heading as="h1" className="text-3xl text-blue-600">
Page Name
</Heading>
</Section>
<Text>
To finish the registration process, click the button below
</Text>
<Button
href={`${url}/auth/verify?token=${token}`}
className="w-24 rounded-md bg-blue-600 px-4 py-3 font-mono tracking-widest text-white"
>
VERIFY
</Button>
<Text>Link is only valid for one hour</Text>
<Section className="mt-6">
<Text className="text-xs">
If you didn't try to login, you can safely ignore this
email.
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
)
}
package.json:
"dependencies": {
"@react-email/components": "^0.0.29",
"next": "15.0.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"resend": "^4.0.1",
"typescript": "5.6.3",
}
If sent as a HTML string, it works:
await resend.emails.send({
to: email,
from: 'Page <[email protected]>',
subject: 'Verify your account',
html: '<p>Test</p>'
})
Expected Behavior
Email sent without an error.
What's your node version? (if relevant)
22.11.0
Hi @wtkw do you have a setup for the code anywhere to check?
Hi @wtkw
It worked when I passed the component like this <Component /> instead of Component.
So maybe you can try this.
await resend.emails.send({
to: email,
from: 'Page <[email protected]>',
subject: 'Verify your account',
react: <VerifyLinkEmail token={token} url={url}/>,
})
Despite to what the documentation says I can only pass it as a function. There is no way I can import it as a Component I think because it is a part of the NextJS POST API function which runs on a server.
Hi @wtkw do you have a setup for the code anywhere to check?
I have it on my private repo, as a part of the authentication route. I could reproduce parts of it on public repo somehow (both backend, frontend, prisma, etc.) but before that I can maybe try to give some more context if this would help?
As mentioned, it is a POST function on NextJS API sign-up route, which gets user's registration data from a form in the frontend:
import { NextResponse } from 'next/server'
import prisma from '@/src/lib/prisma'
import bcrypt from 'bcrypt'
import { resend } from '@/src/lib/resend'
import VerifyLinkEmail from '@/src/lib/email-templates/VerificationEmail'
import jwt, { Secret } from 'jsonwebtoken'
import { getBaseUrl } from '@/src/utils/utils'
export async function POST(req: Request) {
const body = await req.json()
const { name, email, password, confirmPassword } = body
// * Check if user exists and validate input data
try {
const existingUser = await prisma.user.findUnique({
where: {
email,
},
})
if (existingUser) {
return NextResponse.json(
{
message:
'User with this e-mail already exists. Did you forget, you have an account?',
},
{ status: 422 },
)
}
if (password !== confirmPassword) {
return NextResponse.json(
{ message: 'Repeated password incorrect.' },
{ status: 422 },
)
}
} catch (error) {
console.log('[AUTH_SIGNUP_POST]', error)
return NextResponse.json(
{ message: 'Internal authentication error. Try again later.' },
{ status: 500 },
)
}
// * Hash verification code and send verification email
try {
const token = "generatedToken"
const url = getBaseUrl()
await resend.emails.send({
to: email,
from: 'Page <[email protected]>',
subject: 'Verify your account',
react: VerifyLinkEmail({
token,
url,
}),
})
} catch (error) {
console.log('[VERIFICATION_EMAIL]', error)
return NextResponse.json(
{ message: 'Verification e-mail not sent. Try again later.' },
{ status: 500 },
)
}
// * Create new user
try {
const hashedPassword = await bcrypt.hash(body.password, 12)
const newUser = await prisma.user.create({
data: {
email,
hashedPassword,
name,
emailVerified: null,
},
})
return NextResponse.json(newUser)
} catch (error) {
console.log('[AUTH_SIGNUP_POST]', error)
return NextResponse.json(
{ message: 'Verification e-mail not sent. Try again later.' },
{ status: 500 },
)
}
}
after sending the email with a verification token, user is created in the DB to verify and activate the account.
It used to work flawlessly until something got updated (either react or nextjs or resend) and I did not catch it on time with teesting...
happening with me on react-email package:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, props, _owner, _store}). If you meant to render a collection of children, use an array instead.
@wtkw interesting... could you also tell me how you are using the <VerifyLink /> component and its setup. Would love to see what is going on in here.
@dBianchii could you also share the usecase that you are using?
@dBianchii could you also share the usecase that you are using?
Sure. I am just using the react-email package so I can preview my emails. I think sending emails on my backend is working fine
BTW, I am on React19
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
export default function WarnDelayedCriticalTasks({
taskTitle = "Comer macarrĂŁo",
// taskDate = new Date(),
}: {
taskTitle: string | null;
// taskDate: Date;
}) {
return (
<Html>
<Head />
<Preview>{`A tarefa ${taskTitle} está atrasada`}</Preview>
<Tailwind>
<Body className="mx-auto my-auto bg-white font-sans">
Just testing
</Body>
</Tailwind>
</Html>
);
}
@dBianchii that issue seems to be unrelated, but it does mean that there are multiple React versions running at the same time in your case because the element structure changed from React 18 to 19
@wtkw interesting... could you also tell me how you are using the component and its setup. Would love to see what is going on in here.
The user register form triggers an onSubmit function:
const onSubmit: SubmitHandler<Inputs> = async (values) => {
try {
setIsLoading(true)
await axios.post('/api/auth/signup', values)
toast({
variant: 'default',
title: 'User registered successfully',
description: 'Check your email to verify your account',
duration: 9000,
})
router.push(`/auth/verify?email=${values.email}`)
} catch (error: unknown) {
if (error instanceof AxiosError) {
toast({
variant: 'destructive',
title: 'Error ' + error.response?.status,
description: error.response?.data?.message,
duration: 9000,
})
}
} finally {
form.reset()
setIsLoading(false)
}
}
which uses axios (it was written before Server Actions in NextJS became a thing) to send the POST request I pasted above. POST request triggers a serverside function to send an email with resend.
The e-mail is not send correctly due to above mentioned error, so the error response is presented back on the frontend.
Please let me know what other exact information would you need?
I have this workaround on my end.
import { render } from '@react-email/components';
....
const content = await render(MyComponent(props));
await resend.emails.send({
from: EmailFrom,
to: emails,
subject,
html: content,
});
Keep getting the error in this format:
import { render } from '@react-email/components';
....
await resend.emails.send({
from: EmailFrom,
to: emails,
subject,
react: MyComponent(props),
});
I also am seeing this error on the dev server with React 19. Pretty basic repro steps here:
{
"name": "email",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "email dev --port 3030"
},
"devDependencies": {
"react-email": "^3.0.4"
},
"dependencies": {
"@react-email/components": "^0.0.31",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
import { Link, Section, Text, Button } from "@react-email/components";
import React from "react";
const InviteEmail = () => (
<Html>
<Body
<Text style={{ marginBottom: "24px" }}>
Someone invited you to join Example.
To get started, open the{" "}
<Link
href="https://example.com/dashboard"
target="_blank"
>
Example dashboard
</Link>{" "}
and log in:
</Text>
<Section style={{ marginTop: "12px", marginBottom: "32px" }}>
<Button href="https://example.com/login">Log in to Example.com</Button>
</Section>
<Text className="mb-4 text-gray-800">
If you have any questions, feel free to{" "}
<Link
href="mailto:[email protected]"
style={{
textDecoration: "underline",
}}
>
contact us
</Link>{" "}
anytime.
</Text>
</Body>
</Html>
);
export default InviteEmail;
I have this workaround on my end.
Tried that but the render function throws following:
[VERIFICATION_EMAIL] Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, props, _owner, _store}). If you meant to render a collection of children, use an array instead.
at renderNodeDestructiveImpl (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6256:11)
at renderNodeDestructive (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
at renderNode (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
at renderSuspenseBoundary (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:5657:5)
at renderElement (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6071:11)
at renderNodeDestructiveImpl (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
at renderNodeDestructive (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
at retryTask (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6605:5)
at performWork (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6653:7)
at Immediate.<anonymous> (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6980:12)
I have the same issue.
I just created a Stack Overflow question for that here... Im having the same issue with Nodemailer, so I don't think it's related to resend...
Hello Folks!
I just got it working. I simply switched back to using renderAsync (instead of render) with the
react-email/[email protected] version. It seems the "new" render has some issue that I haven't been able to identify yet.
Here's a working code example:
import { renderAsync } from 'npm:@react-email/[email protected]'
import { ReservationConfirmEmail } from './_templates/reservation-code.tsx'
// ...
const html = await renderAsync(React.createElement(ReservationConfirmEmail))
// ... Send html via Nodemailer
Result: Email sent successfully with the template rendered correctly.
Can anyone here make a minimal reproduction of this? With or without the Resend SDK?
I think the issue is with conflicting @babel/core. If you run npm list @babel/core, you'll see something like this:
@remix-run/[email protected]
│ ├─┬ @babel/[email protected] invalid: "7.24.5" from node_modules/react-email
What other react projects do you guys have in your monorepo?
Same here using bun
I ran into this issue in a Turborepo monorepo after upgrading an app to Next 15. Our package email still had a dependency of React 18 from when it was created via create-email. After explicitly upgrading the package to React 19 (along with resend to 4.0.1, the dev server started working again along with sending emails via Resend.
I have a feeling this error is somehow connected to mismatched React versions between what @react-email exports and the React version of the client being used to import it.
I had this problem in my turborepo monorepo. Basically just make sure react dependencies (react and react-dom) are hoisted at root node_modules
I ran into this issue in a Turborepo monorepo after upgrading an app to Next 15. Our package
create-email. After explicitly upgrading the package to React 19 (along withresendto4.0.1, the dev server started working again along with sending emails via Resend.I have a feeling this error is somehow connected to mismatched React versions between what
@react-emailexports and the React version of the client being used to import it.
This fixed it for me. Make sure your the react and react-dom versions are the same in your react-email package.json and your main code package.json.
Ok, I wasted like 2 hours on this and was absolutely baffled at wtf was going on because I don't use React but I use this to generate emails on my backend. After a long rabbit hole here is what was happening. I didn't have react 18.3.1 in my peerDependencies so it was installing React 19 instead. As soon as I specified the peerDependencies and removed the React 19 package from node_modules and did a new npm i it fixed the issue. I still don't 100% understand why it chose React 19 but I am assuming it has something do with how npm resolves dependencies.
in my case I was using renderAsync, I believe it is just the matter how we can carefully match between "react/dom version" with "react-email/components version", previously my react/dom version is 18.3.1, turns out, my react-email/components 0.0.11 is too old
I checked it:
npm view @react-email/[email protected] peerDependencies
{ react: '18.2.0' }
So I tried to use newer version with react v19:
❯ npm view @react-email/[email protected] peerDependencies
{ react: '^18.0 || ^19.0 || ^19.0.0-rc' }
when I see the newer version,
import { renderAsync } from "@react-email/components";
const getData = async (component: JSX.Element) => {
console.log(component)
const html = await renderAsync(component);
return html;
};
"@react-email/components": "^0.0.33", "react": "19.0.0", "react-dom": "19.0.0",
works beautifully. but yeah it has a lot of things to do, when it comes to increase the react version, there are some peer dependencies that needs to be resolved first, take a while for me to get rid of them.
I was running into this error when running the dev server. I had specified [email protected] in my package.json but did not specify a react-dom package. So when I upgraded to [email protected], it had a mismatched dependency with @react-email/render.
Adding [email protected] to my package.json resolved the issue.
I wasted a hour trying to use newer versions of packages. did not work. I am not sure they test any of the stuff. Just stick to something that work.
"react": "npm:[email protected]", "resend": "npm:[email protected]", "@react-email/components": "npm:@react-email/[email protected]"
Anyone found a fix for this ?
ALRIGHT GUYSSSS SO I FOUND SOMETHING. I'm using resend/react-email on honojs with typescript. In tsconfig, I changed my "jsx" field to "react" instead of react-jsx. All I need to do now is import react like this "import React from 'react';" in every file/component where react components are used. tsconfig.json should look like this:
{ "compilerOptions": { "strict": true, "jsx": "react", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "target": "ESNext", "module": "NodeNext", "moduleResolution": "nodenext", "resolveJsonModule": true, "allowImportingTsExtensions": true, "noEmit": true, } }
And add this at the top of every file where components are used/declared:
import React from 'react';
I didn't really dig into the "how and why", but it did fix it for me. Hope this will help someone !
ALRIGHT GUYSSSS SO I FOUND SOMETHING. I'm using resend/react-email on honojs with typescript. In tsconfig, I changed my "jsx" field to "react" instead of react-jsx. All I need to do now is import react like this "import React from 'react';" in every file/component where react components are used. tsconfig.json should look like this:
{ "compilerOptions": { "strict": true, "jsx": "react", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "target": "ESNext", "module": "NodeNext", "moduleResolution": "nodenext", "resolveJsonModule": true, "allowImportingTsExtensions": true, "noEmit": true, } }And add this at the top of every file where components are used/declared:
import React from 'react';I didn't really dig into the "how and why", but it did fix it for me. Hope this will help someone !
My setup is also honojs with bun and resend, and I'm porting loads of code from nextjs there everything was working, the jsx tsconfig has a value of "preserve" in my nextjs tsconfig . Changing the tsconfig like this solved my problem in my hono project
Upgrading to React 19 fixed my issue:
"@react-email/components": "^0.0.36",
"react": "^19.1.0",
"resend": "^4.2.0",