Skip to content

Feat: Enhanced email notifications + reply-by-email support (#811)#821

Open
clbotdev wants to merge 1 commit into
mainfrom
issue-811
Open

Feat: Enhanced email notifications + reply-by-email support (#811)#821
clbotdev wants to merge 1 commit into
mainfrom
issue-811

Conversation

@clbotdev

Copy link
Copy Markdown
Collaborator

What's new

Richer Email Notifications

  • Notification emails now include discussion/comment content with author, context, and CTA
  • Works for both Community Discussions and Product Discussions
  • Rich HTML template with: actor avatar/name, discussion title, parent comment (blockquoted), new comment, community/course context

Reply From Email

  • Users can reply directly from email client — replies auto-post to the thread
  • Signed reply tokens (AES-256-GCM) embedded in Reply-To addresses, valid for 30 days
  • Vendor-agnostic inbound email abstraction supporting: AWS SES, Postmark, Mailgun, SendGrid
  • Inbound email endpoint at POST /api/inbound-email (Next.js) routing to queue service
  • Strips quoted content before saving replies
  • Validates: reply token, sender email, target discussion/comment

Architecture

  • packages/common-models/src/reply-token.ts — shared types
  • packages/common-logic/src/utils/reply-token-utils.ts — token create/verify utilities
  • packages/common-logic/src/utils/get-richer-notification-content.ts — notification context resolver
  • apps/queue/src/inbound-email/ — inbound email provider abstraction, processor, routes
  • apps/queue/src/notifications/services/channels/ — richer email template + enhanced EmailChannel

Technical Details

  • Reply tokens encrypted + signed via AES-256-GCM with REPLY_EMAIL_SECRET env var
  • Mongoose lazy model registration for community/product discussion entities
  • All 700+ existing tests pass, queue builds clean

Closes #811

* or just "email@example.com".
*/
function extractFirstAddress(address: string): string {
const match = address.match(/<([^>]+)>/) || address.match(/([^\s,;]+)/);
Comment on lines +215 to +217
const textPlainMatch = raw.match(
/Content-Type:\s*text\/plain[\s\S]*?(?:\r?\n\r?\n)([\s\S]*?)(?:\r?\n--|$)/i,
);

const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;
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.

Enhanced conversation email notifications and reply-by-email support

2 participants