import {SMTPServer} from "smtp-server"; import {getNDK} from "./utils"; import {generateSecretKey} from "nostr-tools"; import {NDKEvent, NDKKind, NDKPrivateKeySigner} from "@nostr-dev-kit/ndk"; import {PrismaClient} from "@prisma/client"; import {logger} from "./utils/logs"; import {encryptEventForRecipient, parseEmail} from "@arx/utils"; export class NostrSmtpServer { private server: SMTPServer; constructor(db: PrismaClient, port: number) { this.server = new SMTPServer({ authOptional: true, logger: false, onData: (stream, session, callback) => { let mailData = ''; stream.on('data', (chunk: Buffer) => { mailData += chunk.toString(); }); stream.on('end', async () => { if (!session.envelope.mailFrom) { logger.warn('Ignoring email without sender'); callback(); return; } try { const parsedEmail = parseEmail(mailData); for (let recipientEmail of session.envelope.rcptTo) { const address = recipientEmail.address; const parts = address.split('@'); if (parts[1] !== process.env.BASE_DOMAIN) { logger.warn('Not sending email to', address, 'because it is not in the allowed domain'); continue; } const alias = parts[0]; const user = await db.alias.findUnique({ where: { alias }, include: { user: true } }); if (!user) { logger.warn('No user found for', alias, 'skipping'); continue; } const timeRemainingInSubscription = user.user.subscriptionDuration === null ? Infinity : (user.user.subscriptionDuration * 1000) - Date.now() + user.user.lastPayment.getTime(); if (timeRemainingInSubscription <= 0) { logger.warn(`Subscription has expired for ${alias}`); continue; } const recipient = user.npub; const randomKey = generateSecretKey(); const randomKeySinger = new NDKPrivateKeySigner(randomKey); const ndk = getNDK(); ndk.signer = randomKeySinger; await ndk.connect(); const ndkUser = ndk.getUser({ npub: recipient }); const randomKeyUser = await randomKeySinger.user(); const event = new NDKEvent(); event.kind = NDKKind.Article; event.content = parsedEmail.body; event.created_at = Math.floor(Date.now() / 1000); event.pubkey = randomKeyUser.pubkey; event.tags.push(['p', ndkUser.pubkey]) event.tags.push(['subject', parsedEmail.subject]); event.tags.push(['email:localIP', session.localAddress]); event.tags.push(['email:remoteIP', session.remoteAddress]); event.tags.push(['email:isEmail', 'true']); for (let to of session.envelope.rcptTo) event.tags.push(['email:to', to.address]); for (let header of Object.keys(parsedEmail.headers)) event.tags.push([`email:header:${header}`, parsedEmail.headers[header]]); event.tags.push(['email:session', session.id]); event.tags.push(['email:from', session.envelope.mailFrom?.address ?? '']); await event.sign(randomKeySinger); const encryptedEvent = await encryptEventForRecipient(ndk, event, ndkUser); await encryptedEvent.publish(); } } catch (e) { logger.error(JSON.stringify(e)); } finally { callback(); } }); } }); this.server.listen(port, '0.0.0.0'); logger.info(`SMTP Server running on port ${port}`); } }