Skip to content

feat: added smtp support to email#342

Open
arianpotter wants to merge 2 commits intoOpenpanel-dev:mainfrom
arianpotter:feature/send-email-via-smtp
Open

feat: added smtp support to email#342
arianpotter wants to merge 2 commits intoOpenpanel-dev:mainfrom
arianpotter:feature/send-email-via-smtp

Conversation

@arianpotter
Copy link
Copy Markdown

@arianpotter arianpotter commented Apr 18, 2026

Add SMTP support as alternative to Resend (issue #320)

Overview

Email sending now supports two transports: SMTP (via nodemailer) and Resend. If SMTP_HOST is set, SMTP is used. Otherwise it falls back to Resend. If neither is configured, the email payload is logged to console (existing dev behavior).


How it works

SMTP_HOST set? → render template to HTML → send via nodemailer
↓ no
RESEND_API_KEY set? → send via Resend (unchanged behavior)
↓ no
Log to console (dev fallback)


Implementation details

packages/email/src/index.tsx

  • Added @react-email/render to convert React Email JSX templates to plain HTML strings — required for SMTP since nodemailer doesn't understand JSX
  • Added nodemailer for SMTP transport
  • createSmtpTransport() reads SMTP_HOST/PORT/SECURE/USER/PASS from env
  • SMTP check runs before Resend, so it takes priority when both are configured
  • Unsubscribe headers (List-Unsubscribe) are preserved for both transports

New dependencies

  • nodemailer + @types/nodemailer — SMTP client
  • @react-email/render — HTML renderer for React Email templates

Configuration

Variable Required Default Description
SMTP_HOST Yes (to enable SMTP) SMTP server hostname
SMTP_PORT No 587 SMTP port
SMTP_SECURE No false "true" for TLS (port 465)
SMTP_USER No SMTP auth username
SMTP_PASS No SMTP auth password
EMAIL_SENDER No hello@openpanel.dev From address (shared with Resend)

Both .env.example and self-hosting/.env.template have been updated with the new variables and comments explaining the two options.

Summary by CodeRabbit

  • New Features

    • Added SMTP email delivery as an alternative to Resend.
    • Implemented unsubscribe support via RFC 2369-compliant headers for better email management.
  • Configuration

    • Added environment variables and updated templates to configure either Resend or SMTP (host, port, security, credentials, sender).
    • Configuration now validates presence of SMTP or Resend credentials before sending.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 18, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca8c80a4-f5e3-4c70-8058-aee3d7b6f937

📥 Commits

Reviewing files that changed from the base of the PR and between 460c642 and ea34965.

📒 Files selected for processing (1)
  • packages/email/src/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/email/src/index.tsx

📝 Walkthrough

Walkthrough

Adds SMTP as an alternative email delivery backend alongside Resend, updates environment templates/examples, adds SMTP and render dependencies, and implements conditional routing in sendEmail with shared subject/headers and an SMTP transport path that renders React email templates.

Changes

Cohort / File(s) Summary
Env templates
/.env.example, self-hosting/.env.template
Added commented env vars for email delivery: RESEND_API_KEY, EMAIL_SENDER, and SMTP fields SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASS.
Email package deps
packages/email/package.json
Added @react-email/render, nodemailer, and @types/nodemailer to support SMTP sending and server-side template rendering.
Email sending logic
packages/email/src/index.tsx
Extended sendEmail to compute subject/headers once, add SMTP path (renders template to HTML, creates Nodemailer transport via new createSmtpTransport(), sends with headers), retain Resend flow, and update diagnostic checks/log messages.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Email as sendEmail
    participant Renderer as `@react-email/render`
    participant SMTP as Nodemailer
    participant Resend as Resend API

    App->>Email: sendEmail(template, data)
    Email->>Email: build subject
    Email->>Email: generate unsubscribe headers

    alt SMTP_HOST configured
        Email->>Renderer: render template -> HTML
        Renderer-->>Email: HTML
        Email->>SMTP: transport.sendMail({from,to,subject,html,headers})
        SMTP-->>Email: send result
    else RESEND_API_KEY configured
        Email->>Resend: resend.emails.send({to,from,subject,html or template,...})
        Resend-->>Email: send result
    else No provider
        Email-->>App: return null + log error
    end

    Email-->>App: return send result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 I hopped through configs, nibbling a key,

SMTP and Resend now both welcome to tea.
Templates rendered, headers aligned,
Messages whisked off on a tailwind—so kind. 🥕📧

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: added smtp support to email' directly and clearly summarizes the main change—adding SMTP as an alternative email transport—which is the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/email/src/index.tsx (1)

76-85: Add a plain-text body for SMTP emails.

The SMTP path currently sends only HTML. @react-email/render exports toPlainText to derive plain text from rendered HTML, which improves compatibility for clients that prefer or require text bodies.

✉️ Proposed plain-text fallback
-import { render } from '@react-email/render';
+import { render, toPlainText } from '@react-email/render';
       const html = await render(
         <template.Component {...(props.data as any)} />,
       );
+      const text = toPlainText(html);
       const transport = createSmtpTransport();
       const res = await transport.sendMail({
         from: FROM,
         to,
         subject,
         html,
+        text,
         headers,
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/email/src/index.tsx` around lines 76 - 85, The SMTP send currently
only sets html; derive a plain-text body and include it in the sendMail call by
importing and using toPlainText from `@react-email/render` to convert the rendered
html into text, then pass that text as the text property to transport.sendMail
(locate where render(...) produces html for template.Component and where
createSmtpTransport() and transport.sendMail({...}) are called and add the text
field).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/email/src/index.tsx`:
- Around line 18-27: The createSmtpTransport function can mis-handle an empty
SMTP_PORT (Number('') === 0) and lacks explicit timeouts; update
createSmtpTransport (where createTransport is called) to use a safe port parse
like: if process.env.SMTP_PORT is set use Number(process.env.SMTP_PORT)
otherwise default to 587, and add nodemailer timeout options (connectionTimeout,
greetingTimeout, socketTimeout) with sensible values to avoid long stalls; also
keep the existing auth logic and secure parsing as-is.

---

Nitpick comments:
In `@packages/email/src/index.tsx`:
- Around line 76-85: The SMTP send currently only sets html; derive a plain-text
body and include it in the sendMail call by importing and using toPlainText from
`@react-email/render` to convert the rendered html into text, then pass that text
as the text property to transport.sendMail (locate where render(...) produces
html for template.Component and where createSmtpTransport() and
transport.sendMail({...}) are called and add the text field).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0d780cc6-00cd-4d05-bb13-2c9ceaba19aa

📥 Commits

Reviewing files that changed from the base of the PR and between 0e677ba and 460c642.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .env.example
  • packages/email/package.json
  • packages/email/src/index.tsx
  • self-hosting/.env.template

Comment thread packages/email/src/index.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants