/** * Email Notification Channel * Handles sending email notifications via SMTP or SendGrid */ import { EmailNotificationData, NotificationPayload, NotificationType } from '../types'; interface EmailConfig { provider: 'sendgrid' | 'nodemailer' | 'smtp'; apiKey?: string; from: string; replyTo?: string; smtp?: { host: string; port: number; secure: boolean; user: string; pass: string; }; } export class EmailChannel { private config: EmailConfig; constructor(config: EmailConfig) { this.config = config; } /** * Send an email notification */ async send(data: EmailNotificationData): Promise { const emailData = { ...data, from: data.from || this.config.from, replyTo: data.replyTo || this.config.replyTo }; switch (this.config.provider) { case 'sendgrid': await this.sendViaSendGrid(emailData); break; case 'smtp': case 'nodemailer': await this.sendViaSMTP(emailData); break; default: // Development mode - log email console.log('[EmailChannel] Development mode - Email would be sent:', { to: emailData.to, subject: emailData.subject, preview: emailData.text?.substring(0, 100) }); } } /** * Send via SendGrid API */ private async sendViaSendGrid(data: EmailNotificationData): Promise { if (!this.config.apiKey) { throw new Error('SendGrid API key not configured'); } const response = await fetch('https://api.sendgrid.com/v3/mail/send', { method: 'POST', headers: { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ personalizations: [{ to: [{ email: data.to }] }], from: { email: data.from }, reply_to: data.replyTo ? { email: data.replyTo } : undefined, subject: data.subject, content: [ { type: 'text/plain', value: data.text || data.html.replace(/<[^>]*>/g, '') }, { type: 'text/html', value: data.html } ] }) }); if (!response.ok) { const error = await response.text(); throw new Error(`SendGrid error: ${error}`); } } /** * Send via SMTP (using nodemailer-like approach) */ private async sendViaSMTP(data: EmailNotificationData): Promise { // In production, this would use nodemailer // For now, we'll simulate the SMTP send if (!this.config.smtp?.host) { console.log('[EmailChannel] SMTP not configured - simulating send'); return; } // Simulate SMTP connection and send console.log(`[EmailChannel] Sending email via SMTP to ${data.to}`); // In production implementation: // const nodemailer = require('nodemailer'); // const transporter = nodemailer.createTransport({ // host: this.config.smtp.host, // port: this.config.smtp.port, // secure: this.config.smtp.secure, // auth: { // user: this.config.smtp.user, // pass: this.config.smtp.pass // } // }); // await transporter.sendMail(data); } /** * Render email template based on notification type */ async renderTemplate(payload: NotificationPayload): Promise { const templates = { welcome: this.getWelcomeTemplate, plant_registered: this.getPlantRegisteredTemplate, plant_reminder: this.getPlantReminderTemplate, transport_alert: this.getTransportAlertTemplate, farm_alert: this.getFarmAlertTemplate, harvest_ready: this.getHarvestReadyTemplate, demand_match: this.getDemandMatchTemplate, weekly_digest: this.getWeeklyDigestTemplate, system_alert: this.getSystemAlertTemplate }; const templateFn = templates[payload.type] || this.getDefaultTemplate; return templateFn.call(this, payload); } /** * Base email layout */ private getBaseLayout(content: string, payload: NotificationPayload): string { return ` ${payload.title}

LocalGreenChain

${content}
`; } private getWelcomeTemplate(payload: NotificationPayload): string { const content = `

Welcome to LocalGreenChain! đŸŒŋ

Thank you for joining our community of sustainable growers and conscious consumers.

With LocalGreenChain, you can:

  • Track your plants from seed to seed
  • Monitor transport and carbon footprint
  • Connect with local growers and consumers
  • Manage vertical farms with precision
${payload.actionUrl ? `Get Started` : ''} `; return this.getBaseLayout(content, payload); } private getPlantRegisteredTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const content = `

Plant Registered Successfully 🌱

Your plant has been registered on the blockchain.

Plant ID: ${data.plantId || 'N/A'}
Species: ${data.species || 'N/A'}
Variety: ${data.variety || 'N/A'}

You can now track this plant throughout its entire lifecycle.

${payload.actionUrl ? `View Plant Details` : ''} `; return this.getBaseLayout(content, payload); } private getPlantReminderTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const content = `

Plant Care Reminder đŸŒŋ

${payload.title}
${payload.message}
${data.plantName ? `

Plant: ${data.plantName}

` : ''} ${data.action ? `

Recommended Action: ${data.action}

` : ''} ${payload.actionUrl ? `View Plant` : ''} `; return this.getBaseLayout(content, payload); } private getTransportAlertTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const content = `

Transport Update 🚚

${payload.message}
${data.distance ? `
${data.distance} km
Distance
${data.carbonKg || '0'} kg
Carbon Footprint
` : ''} ${payload.actionUrl ? `View Journey` : ''} `; return this.getBaseLayout(content, payload); } private getFarmAlertTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const alertClass = data.severity === 'warning' ? 'alert-warning' : 'alert-info'; const content = `

Farm Alert ${data.severity === 'warning' ? 'âš ī¸' : 'â„šī¸'}

${payload.title}
${payload.message}
${data.zone ? `

Zone: ${data.zone}

` : ''} ${data.recommendation ? `

Recommendation: ${data.recommendation}

` : ''} ${payload.actionUrl ? `View Farm Dashboard` : ''} `; return this.getBaseLayout(content, payload); } private getHarvestReadyTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const content = `

Harvest Ready! 🎉

Great news! Your crop is ready for harvest.
${data.batchId ? `

Batch: ${data.batchId}

` : ''} ${data.cropType ? `

Crop: ${data.cropType}

` : ''} ${data.estimatedYield ? `

Estimated Yield: ${data.estimatedYield}

` : ''}

Log the harvest to update your blockchain records.

${payload.actionUrl ? `Log Harvest` : ''} `; return this.getBaseLayout(content, payload); } private getDemandMatchTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const content = `

Demand Match Found! 🤝

We've found a match between supply and demand.

${payload.message}
${data.matchDetails ? `

Crop: ${data.matchDetails.crop}

Quantity: ${data.matchDetails.quantity}

Region: ${data.matchDetails.region}

` : ''} ${payload.actionUrl ? `View Match Details` : ''} `; return this.getBaseLayout(content, payload); } private getWeeklyDigestTemplate(payload: NotificationPayload): string { const data = payload.data || {}; const content = `

Your Weekly Summary 📊

Here's what happened this week on LocalGreenChain:

${data.plantsRegistered || 0}
Plants Registered
${data.carbonSaved || 0} kg
Carbon Saved
${data.localMiles || 0}
Local Food Miles
${data.highlights ? `

Highlights

    ${data.highlights.map((h: string) => `
  • ${h}
  • `).join('')}
` : ''} ${payload.actionUrl ? `View Full Report` : ''} `; return this.getBaseLayout(content, payload); } private getSystemAlertTemplate(payload: NotificationPayload): string { const content = `

System Notification âš™ī¸

${payload.title}
${payload.message}
${payload.actionUrl ? `Learn More` : ''} `; return this.getBaseLayout(content, payload); } private getDefaultTemplate(payload: NotificationPayload): string { const content = `

${payload.title}

${payload.message}

${payload.actionUrl ? `View Details` : ''} `; return this.getBaseLayout(content, payload); } }