I've been running notionto.email, a service that
sends Notion pages as emails. The hardest part wasn't the Notion API or email
delivery. It was the rendering layer.
So I extracted it and open-sourced it: notion-to-email.
TL;DR
notion-to-email is a TypeScript library that turns any Notion page into
email-compatible HTML, ready to pass to SES, SendGrid, Resend, or Nodemailer.
import { renderFromNotion } from 'notion-to-email'
const { html, title } = await renderFromNotion({
pageId: 'your-page-id',
token: 'your-notion-token',
})
// Pass html to SES, SendGrid, Nodemailer, Resend, etc.
The rendering problem
Email HTML is notoriously finicky. No Flexbox. No Grid. Table-based layouts,
inline styles, and every client renders things slightly differently.
Notion's private images also use short-lived signed URLs, so you need to
re-host them before sending or they'll be broken by the time someone opens
the email.
Installation
npm install notion-to-email @notionhq/client
What's supported
20+ block types, all rendered to table-based email HTML:
- Paragraphs, Headings H1–H4
- Bulleted & Numbered Lists
- To-Do, Toggle, Quote, Callout
- Code blocks, Equations (image fallback)
- Tables, Column layouts
- Images, YouTube thumbnails, File links, Bookmarks
- Synced blocks, Child pages/databases, Link to page, Table of Contents
- Rich text: bold, italic, strikethrough, inline code, colors, links, mentions
Configuration
await renderFromNotion({
pageId: 'your-page-id',
token: 'your-token',
options: {
// Notion private images expire quickly. Re-host them before sending
// so they're still visible when recipients open the email.
// `context` contains { blockId, blockType, pageId, isPublicPage, usage }.
// usage: 'image' | 'cover' | 'icon' | 'file'
resolveImageUrl: (url, context) =>
`https://your-cdn.com/proxy?url=${encodeURIComponent(url)}`,
// Show a "View in Notion" button at the top of the email
header: { showNotionButton: true },
// Append a custom footer (raw HTML)
footer: '<p>Sent via My App</p>',
// 'placeholder' renders a visible fallback for unsupported blocks
// instead of silently dropping them
onUnsupportedBlock: 'placeholder',
},
})
CLI
npx notion-to-email <page-id> --token secret_xxx
npx notion-to-email <page-id> -o email.html
npx notion-to-email https://notion.so/My-Page-abc123
GitHub: https://github.com/Sangkwun/notion-to-email
npm: https://www.npmjs.com/package/notion-to-email
MIT licensed. PRs and issues are always welcome.
Top comments (0)