langfuse/langfuse · error · Error
Email(s) (${failed.join(", ")}) could not be sent
Error message
Email(s) (${failed.join(", ")}) could not be sent What it means
After attempting to send a password-reset email, the sender checks nodemailer's result for rejected or pending recipients; if any recipient failed, it throws listing the failed addresses. The code notes the SES transport omits these fields, so it guards against undefined before reading them — any non-empty rejection or pending list fails the request.
Source
Thrown at packages/shared/src/server/services/email/passwordReset/sendResetPasswordVerificationRequest.tsx:117
const textBody = isSetupMode
? `Welcome to Langfuse! Use the following code to verify your email: ${token}\n\nThis code will expire in 3 minutes. If you did not request this, you can ignore this email.`
: `Use the following code to reset your Langfuse password: ${token}\n\nThis code will expire in 3 minutes. If you did not request a reset, you can ignore this email.`;
const result = await transport.sendMail({
to: identifier,
from: provider.from,
subject,
text: textBody,
html: htmlTemplate,
});
// nodemailer's SES transport omits `rejected`/`pending` from SentMessageInfo,
// so guard against undefined before reading them.
const failed = [...(result.rejected ?? []), ...(result.pending ?? [])].filter(
Boolean,
);
if (failed.length) {
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`);
}
}
export default ResetPasswordTemplate;
View on GitHub (pinned to 59d92c7cf3)
Solutions
- Verify the recipient email address is valid and deliverable before sending (format + MX check)
- Check SMTP/SES provider logs and dashboards for the rejection reason (bounce, greylist, policy)
- If greylisting/pending is transient, retry after a delay or let the user resend
- Confirm transport configuration (host, port, auth) points at the intended relay
Defensive patterns
Strategy: try-catch
Validate before calling
const emailSchema = z.string().email();
if (!emailSchema.safeParse(user.email).success) {
return { ok: false, reason: "invalid-email" };
} Type guard
const hasRejectedRecipients = (
info: SentMessageInfo
): info is SentMessageInfo & { rejected: string[] } =>
Array.isArray(info.rejected) && info.rejected.length > 0; Try / catch
try {
await sendResetPasswordVerificationRequest({ email });
} catch (err) {
if (err instanceof Error && /could not be sent/.test(err.message)) {
res.status(422).json({ error: "We could not deliver a reset email to that address. Verify it and try again." });
return;
}
throw err;
} Prevention
- Validate email syntax and MX records before enqueueing sends
- Monitor bounces/complaints via SES SNS feedback and suppress repeat-failing addresses
- Use a dedicated retry queue for transient SMTP failures (greylisting) separate from hard rejections
When it happens
Trigger: Calling sendResetPasswordVerificationRequest where the SMTP/transport server rejects the recipient address (e.g. 550 unknown user, invalid domain) or leaves it pending; result.rejected or result.pending then contains the email address.
Common situations: User signs up with a typo'd or nonexistent email then requests a password reset; SMTP relay greylists/defers the message (pending); mailbox full or domain MX missing; transport misconfiguration causing partial failures.
AI-assisted analysis of langfuse/langfuse@59d92c7cf3 (2026-08-27).
Data as JSON: /api/errors/c54d5038d2ba0e86.
Report an issue: GitHub.