Objects are not valid as a React child Error when sending react-email with resend
Opened this issue · 19 comments
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 <noreply@page.net>',
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 <noreply@page.net>',
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 <noreply@page.net>',
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 <noreply@page.net>',
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...
@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.
@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:support@example.com"
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\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6256:11)
at renderNodeDestructive (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
at renderNode (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
at renderSuspenseBoundary (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:5657:5)
at renderElement (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6071:11)
at renderNodeDestructiveImpl (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
at renderNodeDestructive (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
at retryTask (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\node_modules\react-dom\cjs\react-dom-server.node.development.js:6605:5)
at performWork (C:\Users\User\Projects\projects\page\node_modules\.pnpm\react-dom@18.3.1_react@18.3.1\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\react-dom@18.3.1_react@18.3.1\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/components@0.0.22
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/components@0.0.22'
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/dev@2.15.1
│ ├─┬ @babel/core@7.26.0 invalid: "7.24.5" from node_modules/react-email
What other react projects do you guys have in your monorepo?
Same here using bun