diff --git a/quartz/components/ReplyByEmail.tsx b/quartz/components/ReplyByEmail.tsx
new file mode 100644
index 000000000..b8f8450ef
--- /dev/null
+++ b/quartz/components/ReplyByEmail.tsx
@@ -0,0 +1,142 @@
+import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
+import { classNames } from "../util/lang"
+
+interface ReplyByEmailOptions {
+ username?: string
+ domain?: string
+ includeTitles?: string[]
+ excludeTitles?: string[]
+}
+
+// Default options will be used if not provided in the layout file
+const defaultOptions: ReplyByEmailOptions = {
+ username: "ZW1haWw=", // "email" in base64
+ domain: "ZXhhbXBsZS5jb20=", // "email.com" in base64
+ includeTitles: [],
+ excludeTitles: ["Home", "About me", "Contact me"]
+}
+
+const ReplyByEmail: QuartzComponent = ({
+ fileData,
+ displayClass,
+ username,
+ domain,
+ includeTitles,
+ excludeTitles
+}: QuartzComponentProps & ReplyByEmailOptions) => {
+ const title = fileData.frontmatter?.title
+
+ // Use provided values or defaults
+ const encodedPart1 = username || defaultOptions.username
+ const encodedPart2 = domain || defaultOptions.domain
+ const includeList = includeTitles || defaultOptions.includeTitles
+ const excludeList = excludeTitles || defaultOptions.excludeTitles
+
+ // Display logic:
+ // 1. If includeTitles is not empty, only show on those pages
+ // 2. If includeTitles is empty, show on all pages except those in excludeTitles
+ const shouldDisplay = title && (
+ (includeList.length > 0 && includeList.includes(title)) ||
+ (includeList.length === 0 && !excludeList.includes(title))
+ )
+
+ if (shouldDisplay) {
+ return (
+
+
+
+ )
+ } else {
+ return null
+ }
+}
+
+ReplyByEmail.css = `
+.center-wrapper {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 2rem;
+}
+
+.reply-by-email-button {
+ display: inline-block;
+ padding: 0.5rem 1rem;
+ background-color: var(--highlight);
+ color: var(--secondary);
+ border-radius: 5px;
+ transition: background-color 0.2s ease, transform 0.2s ease;
+ cursor: pointer;
+ border: none;
+ font-size: 1rem;
+ font-family: inherit;
+}
+
+.reply-by-email-button:hover {
+ transform: scale(1.05);
+}
+`
+
+// Script that works with SPA navigation
+ReplyByEmail.beforeDOMLoaded = `
+// Function to attach email button handlers
+function attachEmailHandlers() {
+ document.querySelectorAll('.reply-by-email-button').forEach(function(button) {
+ // Remove existing event listeners first to prevent duplicates
+ button.removeEventListener('click', handleEmailButtonClick);
+ // Add fresh event listener
+ button.addEventListener('click', handleEmailButtonClick);
+ });
+}
+
+// Handler function for the email button click
+function handleEmailButtonClick(e) {
+ e.preventDefault();
+
+ // Get data attributes
+ const username = atob(this.getAttribute('data-username'));
+ const domain = atob(this.getAttribute('data-domain'));
+ const title = this.getAttribute('data-title');
+
+ // Create email address and mailto link
+ const email = username + '@' + domain;
+ const mailtoLink = 'mailto:' + email + '?subject=' + title;
+
+ // Open email client
+ window.location.href = mailtoLink;
+}
+
+// Initial attachment when the page loads
+document.addEventListener('DOMContentLoaded', attachEmailHandlers);
+
+// Re-attach handlers after SPA navigation
+document.addEventListener('nav', function() {
+ // Small delay to ensure the new buttons are in the DOM
+ setTimeout(attachEmailHandlers, 10);
+});
+`
+
+export default ((opts?: ReplyByEmailOptions) => {
+ // Component constructor that accepts options
+ const component: QuartzComponent = (props) => {
+ return ReplyByEmail({
+ ...props,
+ username: opts?.username,
+ domain: opts?.domain,
+ includeTitles: opts?.includeTitles,
+ excludeTitles: opts?.excludeTitles
+ })
+ }
+
+ // Pass through the CSS and beforeDOMLoaded
+ component.css = ReplyByEmail.css
+ component.beforeDOMLoaded = ReplyByEmail.beforeDOMLoaded
+
+ return component
+}) satisfies QuartzComponentConstructor
diff --git a/quartz/components/index.ts b/quartz/components/index.ts
index cece8e614..b5aade6fb 100644
--- a/quartz/components/index.ts
+++ b/quartz/components/index.ts
@@ -23,6 +23,7 @@ import Breadcrumbs from "./Breadcrumbs"
import Comments from "./Comments"
import Flex from "./Flex"
import ConditionalRender from "./ConditionalRender"
+import ReplyByEmail from "./ReplyByEmail"
export {
ArticleTitle,
@@ -50,4 +51,5 @@ export {
Comments,
Flex,
ConditionalRender,
+ ReplyByEmail,
}