mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
Add missing changelog entries to content/pages/changelog-page.md: v1.34.0 (2025-12-26): Blog page featured layout with hero post - blogFeatured frontmatter field for posts - Hero card displays first featured post with landscape image - 2-column featured row for remaining featured posts - 3-column grid for regular posts v1.35.0 (2025-12-26): Image support at top of posts and pages - showImageAtTop frontmatter field - Full-width image display above post header - Works for both posts and pages v1.36.0 (2025-12-27): Social footer component - Customizable social links (8 platform types) - Copyright with auto-updating year - showSocialFooter frontmatter field for per-page control - Configurable via siteConfig.socialFooter v1.37.0 (2025-12-27): Newsletter Admin UI - Three-column admin interface at /newsletter-admin - Subscriber management with search and filters - Send newsletter panel (post selection or custom email) - Weekly digest automation (Sunday 9am UTC) - Developer notifications (subscriber alerts, weekly stats) - Markdown-to-HTML conversion for custom emails
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
"use node";
|
|
|
|
import { internalAction } from "./_generated/server";
|
|
import { v } from "convex/values";
|
|
import { internal } from "./_generated/api";
|
|
import { AgentMailClient } from "agentmail";
|
|
|
|
// Send contact form email via AgentMail SDK
|
|
// Internal action that sends email to configured recipient
|
|
// Uses official AgentMail SDK: https://docs.agentmail.to/quickstart
|
|
export const sendContactEmail = internalAction({
|
|
args: {
|
|
messageId: v.id("contactMessages"),
|
|
name: v.string(),
|
|
email: v.string(),
|
|
message: v.string(),
|
|
source: v.string(),
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const apiKey = process.env.AGENTMAIL_API_KEY;
|
|
const inbox = process.env.AGENTMAIL_INBOX;
|
|
// Contact form sends to AGENTMAIL_CONTACT_EMAIL or falls back to inbox
|
|
const recipientEmail = process.env.AGENTMAIL_CONTACT_EMAIL || inbox;
|
|
|
|
// Silently fail if environment variables not configured
|
|
if (!apiKey || !inbox || !recipientEmail) {
|
|
return null;
|
|
}
|
|
|
|
// Build email HTML
|
|
const html = `
|
|
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto;">
|
|
<h2 style="font-size: 20px; color: #1a1a1a; margin-bottom: 16px;">New Contact Form Submission</h2>
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
<tr>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid #eee; font-weight: 600; width: 100px;">From:</td>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid #eee;">${escapeHtml(args.name)}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid #eee; font-weight: 600;">Email:</td>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid #eee;"><a href="mailto:${escapeHtml(args.email)}">${escapeHtml(args.email)}</a></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid #eee; font-weight: 600;">Source:</td>
|
|
<td style="padding: 8px 0; border-bottom: 1px solid #eee;">${escapeHtml(args.source)}</td>
|
|
</tr>
|
|
</table>
|
|
<h3 style="font-size: 16px; color: #1a1a1a; margin: 24px 0 8px 0;">Message:</h3>
|
|
<div style="background: #f9f9f9; padding: 16px; border-radius: 6px; white-space: pre-wrap;">${escapeHtml(args.message)}</div>
|
|
</div>
|
|
`;
|
|
|
|
// Plain text version
|
|
const text = `New Contact Form Submission\n\nFrom: ${args.name}\nEmail: ${args.email}\nSource: ${args.source}\n\nMessage:\n${args.message}`;
|
|
|
|
try {
|
|
// Initialize AgentMail client with API key
|
|
const client = new AgentMailClient({ apiKey });
|
|
|
|
// Send email using official SDK
|
|
// https://docs.agentmail.to/sending-receiving-email
|
|
await client.inboxes.messages.send(inbox, {
|
|
to: recipientEmail,
|
|
subject: `Contact: ${args.name} via ${args.source}`,
|
|
text,
|
|
html,
|
|
});
|
|
|
|
// Mark email as sent in database
|
|
await ctx.runMutation(internal.contact.markEmailSent, {
|
|
messageId: args.messageId,
|
|
});
|
|
} catch {
|
|
// Silently fail on error
|
|
}
|
|
|
|
return null;
|
|
},
|
|
});
|
|
|
|
// Helper function to escape HTML entities
|
|
function escapeHtml(text: string): string {
|
|
return text
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|