61 Commits

Author SHA1 Message Date
Your Name
da50bf4773 Add OTP email monitor to handle Museum skipped emails
- Implement comprehensive OTP email monitoring service
- Monitor Museum logs for "Skipping sending email" pattern
- Send verification emails using Cloudron email addon
- Add specific regex pattern for Museum's skip email format
- Version bump to 0.1.62

The monitor captures OTP codes from logs when Museum skips sending
emails and sends them via Cloudron's email system. This ensures
users receive their verification codes even when Museum's email
configuration is not sending directly.
2025-07-22 12:27:44 -06:00
Your Name
4290a33ba9 Fix JavaScript URL construction error for API endpoint
- Change NEXT_PUBLIC_ENTE_ENDPOINT from "/api" to "https://example.com/api" during build to satisfy URL constructor requirements
- Add runtime replacement in start.sh to replace placeholder with actual domain endpoint
- This resolves the "TypeError: Failed to construct 'URL': Invalid URL" error in the frontend

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-22 08:58:53 -06:00
Your Name
62b6f7f9ac Fix S3 configuration - set are_local_buckets to true
- Changed are_local_buckets from false to true (required for external S3)
- Simplified S3 configuration to only use b2-eu-cen bucket
- Removed unnecessary replication buckets for single bucket setup

This aligns with Ente's documentation where are_local_buckets=true
is used for external S3 services like Wasabi.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 20:44:19 -06:00
Your Name
e3eb1b0491 Hardcode Wasabi S3 configuration with proper Ente format
- Remove dynamic S3 configuration loading
- Hardcode Wasabi credentials as requested
- Use proper Ente S3 configuration format with datacenter names
- Configure all three storage buckets (b2-eu-cen, wasabi-eu-central-2-v3, scw-eu-fr-v3)
- Set are_local_buckets to false for external S3
- Add compliance flag for Wasabi bucket

This should fix the MissingRegion error by properly configuring S3 storage
according to Ente's expected format.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 20:41:58 -06:00
Your Name
fc82e988e9 Update CloudronManifest version to 1.0.1
Increment version after multiple iterations of S3 configuration fixes and port conflict resolution.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 20:37:07 -06:00
Your Name
5068e12025 Fix port conflict between Museum server and Caddy
- Changed Museum server to run on port 8080 instead of 3080
- Updated all health check URLs to use port 8080
- Updated Caddy reverse proxy to forward API requests to port 8080
- Added clarifying comment about port usage

This resolves the circular reference where both Caddy and Museum were trying to use port 3080.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 17:18:51 -06:00
Andreas Düren
4ff99bee64 Complete rewrite of Ente Cloudron app with cleaner architecture 2025-03-20 17:52:23 +01:00
Andreas Düren
fd028ca591 Fix syntax error in validate_binary function 2025-03-20 17:38:20 +01:00
Andreas Düren
fd60e4425b Fix Museum binary validation and add Node.js fallback server 2025-03-20 17:29:12 +01:00
Andreas Düren
2d68e44208 Remove Node.js placeholder server completely and use actual Museum server 2025-03-20 17:14:12 +01:00
Andreas Düren
fdbb6d9a7a Enhance Node.js placeholder server with more API endpoints and improve user experience 2025-03-20 17:07:47 +01:00
Andreas Düren
fc7135d483 Fix Museum server binary issues using Docker approach 2025-03-20 16:35:30 +01:00
Andreas Düren
e95fd0a705 Fix Museum server binary issues and add Node.js fallback 2025-03-20 16:19:31 +01:00
Andreas Düren
f27720d544 Replace Node.js placeholder with actual Museum server 2025-03-20 16:10:30 +01:00
Andreas Düren
950481b6c7 Fix infinite loop and implement reliable Node.js placeholder server 2025-03-20 16:03:16 +01:00
Andreas Düren
4d66067d20 Fix infinite loop and implement reliable Node.js placeholder server 2025-03-20 15:59:51 +01:00
Andreas Düren
4081e89fdd Fix Go build environment and frontend URLs 2025-03-20 15:52:35 +01:00
Andreas Düren
d69ab22967 Fix GitHub download URLs and implement placeholder server on port 3080 2025-03-20 15:41:24 +01:00
Andreas Düren
2424a5ffc1 Fix GitHub download issues and provide fallback servers 2025-03-20 15:37:05 +01:00
Andreas Düren
6fd3bde19a Fix GitHub credentials issue, support s3.env, and ensure Caddy properly starts on port 3080 2025-03-20 15:33:49 +01:00
Andreas Düren
144f2b78d1 Add extensive debugging and more resilient startup 2025-03-20 15:29:43 +01:00
Andreas Düren
d828bf3b8e Remove mock server components and install real Ente server 2025-03-20 15:12:30 +01:00
Andreas Düren
3c8309dffd Fix directory creation for static assets and web app files 2025-03-20 14:48:06 +01:00
Andreas Düren
7f7ae4e8bf Implement comprehensive SRP Buffer polyfill to fix verification errors 2025-03-20 14:40:50 +01:00
Andreas Düren
6289577898 Implement Caddy for web app serving and fix OTP verification issues 2025-03-20 14:32:26 +01:00
Andreas Düren
8df2a3a621 Add browser compatibility fixes for URL constructor and Node.js functions 2025-03-20 14:18:30 +01:00
Andreas Düren
192070ffae Fix URL construction error and update verification endpoint with proper schema 2025-03-20 13:54:41 +01:00
Andreas Düren
e69166fc91 Replace Go mock server with Node.js implementation for better reliability 2025-03-20 13:45:58 +01:00
Andreas Düren
f32919d436 Completely refactored startup script for proper museum server integration 2025-03-20 13:36:52 +01:00
Andreas Düren
d345b2f460 Fix API server and URL handling for frontend connectivity 2025-03-20 13:22:13 +01:00
Andreas Düren
f4fd4fdf77 Fix mock API server initialization and unbound variable issues 2025-03-20 13:04:11 +01:00
Andreas Düren
defe47f78d Fix here-document syntax issues in runtime config generation and Go module setup for mock API server 2025-03-20 12:49:06 +01:00
Andreas Düren
5dbbb094b4 Fix here-document syntax error in runtime config generation and Go module initialization 2025-03-20 12:34:05 +01:00
Andreas Düren
50a19a7908 Fix Go module structure for mock servers to resolve build issues 2025-03-18 21:52:41 +01:00
Andreas Düren
c00be35fc7 Fix mock API server startup issues on port 8080 2025-03-18 21:39:20 +01:00
Andreas Düren
b223843bcd Fix mock server startup to ensure it starts properly and binds to the correct ports 2025-03-18 21:23:24 +01:00
Andreas Düren
d32c366683 Fix verification parsing and make code validation more forgiving for testing 2025-03-18 20:54:41 +01:00
Andreas Düren
f545b8d797 Fix URL construction error by ensuring proper URL formats with protocol prefixes 2025-03-18 20:47:23 +01:00
Andreas Düren
1244467afa Fix syntax errors in mock servers and use HEREDOC with quoted delimiter to prevent shell interpretation issues 2025-03-18 20:42:29 +01:00
Andreas Düren
17839a17df Fix syntax errors in mock API server Go code 2025-03-18 20:36:54 +01:00
Andreas Düren
aefea17f2f Replace hardcoded API URLs with dynamic CLOUDRON_APP_ORIGIN variable 2025-03-18 20:29:45 +01:00
Andreas Düren
4811e0986e Update OTT handler to include required ID field in response 2025-03-18 20:28:45 +01:00
Andreas Düren
9709ebe265 Fixed signup verification code by adding a handler for /users/ott endpoint 2025-03-18 20:22:14 +01:00
Andreas Düren
71db4afae1 Fixed empty HTML issue by copying and modifying the original HTML files 2025-03-18 20:16:12 +01:00
Andreas Düren
bdcf96150f Fixed Caddy filter directive and Go import issues 2025-03-18 20:12:30 +01:00
Andreas Düren
43cb685842 Fixed read-only filesystem issues by using Caddy's filter directives and improved mock servers 2025-03-18 20:08:15 +01:00
Andreas Düren
ded9e1d174 Added registration code display in logs 2025-03-18 20:04:02 +01:00
Andreas Düren
e093bfc571 Fixed frontend URL error by injecting config.js and runtime-config.js before Caddy starts 2025-03-18 20:03:16 +01:00
Andreas Düren
e329b54b8b Fixed Caddy config and Go module import issues 2025-03-18 19:58:49 +01:00
Andreas Düren
20c0f80de0 Fixed Caddy config and file permissions issues 2025-03-18 19:55:11 +01:00
Andreas Düren
2fac328b3c Added MIME type configuration for Next.js assets in Caddy 2025-03-18 19:51:36 +01:00
Andreas Düren
b2767897b2 Fixed mock servers by removing module flags and binding to all network interfaces 2025-03-18 19:43:42 +01:00
Andreas Düren
74331a7fe9 Fixed mock servers by removing module dependencies 2025-03-18 19:37:57 +01:00
Andreas Düren
98431a35dc Implemented mock servers instead of trying to run Ente 2025-03-18 19:32:47 +01:00
Andreas Düren
98ccff7af9 Fixed directory permissions and Go module handling 2025-03-18 19:26:22 +01:00
Andreas Düren
546fe4fe5d Fixed Go compiler errors and Caddy header syntax 2025-03-18 19:20:49 +01:00
Andreas Düren
428b7f0ea3 Fixed creation of db_override.go in writable location 2025-03-18 19:15:11 +01:00
Andreas Düren
4819bda8ad Fixed Caddy header syntax and moved db_override.go creation before server startup 2025-03-18 19:10:13 +01:00
Andreas Düren
783ad628b3 Fixed shell script syntax errors and created missing db_override.go file 2025-03-18 18:56:10 +01:00
Andreas Düren
a73d2b4959 Fixed filesystem access issues and network binding for dual-instance Ente setup 2025-03-18 18:48:26 +01:00
Andreas Düren
42c1374606 Add Caddy webserver implementation 2025-03-17 00:13:38 +01:00
5 changed files with 1236 additions and 633 deletions

View File

@@ -7,13 +7,14 @@
"contactEmail": "contact@ente.io",
"tagline": "Open Source End-to-End Encrypted Photos & Authentication",
"upstreamVersion": "1.0.0",
"version": "1.0.0",
"healthCheckPath": "/healthcheck",
"version": "0.1.62",
"healthCheckPath": "/ping",
"httpPort": 3080,
"memoryLimit": 1073741824,
"addons": {
"localstorage": {},
"postgresql": {},
"email": {},
"sendmail": {
"supportsDisplayName": true
}

View File

@@ -1,3 +1,19 @@
# Build Museum server from source
FROM golang:1.24-bookworm AS museum-builder
WORKDIR /ente
# Clone the repository for server building
RUN apt-get update && apt-get install -y git libsodium-dev && \
git clone --depth=1 https://github.com/ente-io/ente.git . && \
apt-get clean && apt-get autoremove && \
rm -rf /var/cache/apt /var/lib/apt/lists
# Build the Museum server
WORKDIR /ente/server
RUN go mod download && \
CGO_ENABLED=1 GOOS=linux go build -a -o museum ./cmd/museum
FROM node:20-bookworm-slim as web-builder
WORKDIR /ente
@@ -12,10 +28,10 @@ RUN apt-get update && apt-get install -y git && \
RUN corepack enable
# Set environment variables for web app build
# Use "/api" as the endpoint which will be replaced at runtime with the full URL
ENV NEXT_PUBLIC_ENTE_ENDPOINT="/api"
# Set the API endpoint to use current origin - this will work at runtime
ENV NEXT_PUBLIC_ENTE_ENDPOINT="https://example.com/api"
# Add a note for clarity
RUN echo "Building with NEXT_PUBLIC_ENTE_ENDPOINT=/api, will be replaced at runtime with full URL"
RUN echo "Building with placeholder NEXT_PUBLIC_ENTE_ENDPOINT, will be served by Caddy proxy at /api"
# Debugging the repository structure
RUN find . -type d -maxdepth 3 | sort
@@ -78,10 +94,16 @@ RUN mkdir -p /build/web/photos /build/web/accounts /build/web/auth /build/web/ca
FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c6637eaf4a41ec4e596c
# Install necessary packages
# Install necessary packages and Caddy webserver
RUN apt-get update && \
apt-get install -y curl git nodejs npm libsodium23 libsodium-dev pkg-config postgresql-client && \
npm install -g yarn serve && \
# Install Caddy for web server
apt-get install -y debian-keyring debian-archive-keyring apt-transport-https && \
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg && \
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list && \
apt-get update && \
apt-get install -y caddy && \
apt-get clean && apt-get autoremove && \
rm -rf /var/cache/apt /var/lib/apt/lists
@@ -94,7 +116,7 @@ RUN curl -L https://go.dev/dl/go1.24.1.linux-amd64.tar.gz -o go.tar.gz && \
ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
# Set up directory structure
RUN mkdir -p /app/code /app/data/config /app/web
RUN mkdir -p /app/code /app/data/config /app/data/caddy /app/web
WORKDIR /app/code
@@ -130,6 +152,11 @@ COPY --from=web-builder /build/web/accounts /app/web/accounts
COPY --from=web-builder /build/web/auth /app/web/auth
COPY --from=web-builder /build/web/cast /app/web/cast
# Copy Museum server binary from builder stage to app directory (not data volume)
RUN mkdir -p /app/museum-bin
COPY --from=museum-builder /ente/server/museum /app/museum-bin/museum
RUN chmod +x /app/museum-bin/museum
# Copy configuration and startup scripts
ADD start.sh /app/pkg/
ADD config.template.yaml /app/pkg/
@@ -137,7 +164,9 @@ ADD config.template.yaml /app/pkg/
# Set proper permissions
RUN chmod +x /app/pkg/start.sh
# Expose the API port
# Expose the web port (Cloudron expects port 3080)
EXPOSE 3080
# Also expose API port
EXPOSE 8080
# Start the application

389
otp-email-monitor.js Normal file
View File

@@ -0,0 +1,389 @@
#!/usr/bin/env node
/**
* Ente OTP Email Monitor
*
* Monitors Museum server logs for OTP generation events and sends
* verification emails using Cloudron's email addon.
*/
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const nodemailer = require('nodemailer');
// Configuration
const CONFIG = {
LOG_FILE: '/app/data/logs/museum.log',
EMAIL_TEMPLATES_DIR: '/app/data/ente/server/mail-templates',
FROM_EMAIL: `noreply@${process.env.CLOUDRON_EMAIL_DOMAIN || 'localhost'}`,
FROM_NAME: 'Ente Photos',
SMTP: {
host: process.env.CLOUDRON_EMAIL_SMTP_SERVER,
port: parseInt(process.env.CLOUDRON_EMAIL_SMTP_PORT) || 587,
secure: false, // STARTTLS disabled on this port
auth: false // Internal mail server
}
};
// Logging utility
class Logger {
static log(level, message, data = null) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [OTP-EMAIL-${level}] ${message}`;
console.log(logMessage);
if (data) {
console.log(JSON.stringify(data, null, 2));
}
// Also write to file
try {
fs.appendFileSync('/app/data/logs/otp-email.log', logMessage + '\n');
} catch (err) {
console.error('Failed to write to log file:', err.message);
}
}
static info(message, data) { this.log('INFO', message, data); }
static warn(message, data) { this.log('WARN', message, data); }
static error(message, data) { this.log('ERROR', message, data); }
}
// Email template handler
class EmailTemplate {
constructor(templateDir) {
this.templateDir = templateDir;
this.templates = new Map();
this.loadTemplates();
}
loadTemplates() {
try {
const ottTemplate = fs.readFileSync(path.join(this.templateDir, 'ott.html'), 'utf8');
const changeEmailTemplate = fs.readFileSync(path.join(this.templateDir, 'ott_change_email.html'), 'utf8');
this.templates.set('ott', ottTemplate);
this.templates.set('ott_change_email', changeEmailTemplate);
Logger.info('Email templates loaded successfully');
} catch (err) {
Logger.error('Failed to load email templates:', err.message);
throw err;
}
}
render(templateName, variables) {
const template = this.templates.get(templateName);
if (!template) {
throw new Error(`Template ${templateName} not found`);
}
let html = template;
// Replace template variables
for (const [key, value] of Object.entries(variables)) {
const placeholder = `{{.${key}}}`;
html = html.replace(new RegExp(placeholder, 'g'), value);
}
return html;
}
}
// Email sender using Cloudron email addon
class EmailSender {
constructor(config) {
this.config = config;
this.transporter = null;
this.initializeTransporter();
}
initializeTransporter() {
try {
this.transporter = nodemailer.createTransport({
host: this.config.SMTP.host,
port: this.config.SMTP.port,
secure: this.config.SMTP.secure,
// No auth needed for internal Cloudron mail server
tls: {
rejectUnauthorized: false // Accept self-signed certificates
}
});
Logger.info('Email transporter initialized', {
host: this.config.SMTP.host,
port: this.config.SMTP.port
});
} catch (err) {
Logger.error('Failed to initialize email transporter:', err.message);
throw err;
}
}
async sendEmail(to, subject, html) {
try {
const mailOptions = {
from: `${this.config.FROM_NAME} <${this.config.FROM_EMAIL}>`,
to: to,
subject: subject,
html: html
};
const result = await this.transporter.sendMail(mailOptions);
Logger.info('Email sent successfully', {
to: to,
subject: subject,
messageId: result.messageId
});
return result;
} catch (err) {
Logger.error('Failed to send email:', {
error: err.message,
to: to,
subject: subject
});
throw err;
}
}
}
// Log monitor for OTP events
class LogMonitor {
constructor(logFile, emailSender, emailTemplate) {
this.logFile = logFile;
this.emailSender = emailSender;
this.emailTemplate = emailTemplate;
this.tail = null;
this.processedOTPs = new Set(); // Prevent duplicate sends
}
start() {
Logger.info('Starting log monitor', { logFile: this.logFile });
// Use tail -F to follow log file
this.tail = spawn('tail', ['-F', this.logFile]);
this.tail.stdout.on('data', (data) => {
const lines = data.toString().split('\n');
for (const line of lines) {
if (line.trim()) {
this.processLogLine(line);
}
}
});
this.tail.stderr.on('data', (data) => {
Logger.warn('Tail stderr:', data.toString());
});
this.tail.on('close', (code) => {
Logger.warn('Tail process closed', { code });
// Restart after 5 seconds
setTimeout(() => this.start(), 5000);
});
this.tail.on('error', (err) => {
Logger.error('Tail process error:', err.message);
setTimeout(() => this.start(), 5000);
});
}
stop() {
if (this.tail) {
this.tail.kill();
this.tail = null;
Logger.info('Log monitor stopped');
}
}
processLogLine(line) {
try {
// Look for OTP-related log entries
// Museum server logs OTP generation in various formats
const patterns = [
// Pattern 1: Museum skipping email - MOST IMPORTANT (matches "Skipping sending email to andreas@due.ren: Verification code: 192305")
/Skipping sending email to\s+([^\s:]+):\s*Verification code:\s*(\d{6})/i,
// Pattern 2: Direct OTP generation logs
/sendOTT.*email[:\s]+([^\s]+).*code[:\s]+(\d{6})/i,
// Pattern 3: User registration/login with OTP
/generateOTT.*user[:\s]+([^\s]+).*verification.*code[:\s]+(\d{6})/i,
// Pattern 4: Email change OTP
/changeEmail.*email[:\s]+([^\s]+).*otp[:\s]+(\d{6})/i,
// Pattern 5: Generic OTP patterns in logs
/ott.*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}).*(\d{6})/i
];
for (const pattern of patterns) {
const match = line.match(pattern);
if (match) {
const email = match[1];
const otpCode = match[2];
// Create unique identifier to prevent duplicates
const otpId = `${email}:${otpCode}:${Date.now().toString().slice(-6)}`;
if (!this.processedOTPs.has(otpId)) {
this.processedOTPs.add(otpId);
this.sendOTPEmail(email, otpCode, line);
// Clean up old OTPs (keep last 100)
if (this.processedOTPs.size > 100) {
const oldOTPs = Array.from(this.processedOTPs).slice(0, 50);
oldOTPs.forEach(otp => this.processedOTPs.delete(otp));
}
}
break;
}
}
} catch (err) {
Logger.error('Error processing log line:', {
error: err.message,
line: line.substring(0, 100)
});
}
}
async sendOTPEmail(email, otpCode, logLine) {
try {
Logger.info('Processing OTP email request', {
email: email,
otpCode: otpCode.substring(0, 2) + '****', // Partial OTP for logging
source: logLine.substring(0, 100)
});
// Determine template type based on context
let templateName = 'ott';
let subject = 'Ente - Verification Code';
if (logLine.toLowerCase().includes('change') || logLine.toLowerCase().includes('email')) {
templateName = 'ott_change_email';
subject = 'Ente - Email Change Verification';
}
// Render email template
const html = this.emailTemplate.render(templateName, {
VerificationCode: otpCode
});
// Send email
await this.emailSender.sendEmail(email, subject, html);
Logger.info('OTP email sent successfully', {
email: email,
template: templateName
});
} catch (err) {
Logger.error('Failed to send OTP email:', {
error: err.message,
email: email,
otpCode: otpCode.substring(0, 2) + '****'
});
}
}
}
// Health check endpoint
class HealthServer {
constructor(port = 8081) {
this.port = port;
this.server = null;
}
start() {
const http = require('http');
this.server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'healthy',
service: 'ente-otp-email-monitor',
timestamp: new Date().toISOString(),
processedOTPs: monitor ? monitor.processedOTPs.size : 0
}));
} else {
res.writeHead(404);
res.end('Not Found');
}
});
this.server.listen(this.port, () => {
Logger.info(`Health server listening on port ${this.port}`);
});
}
stop() {
if (this.server) {
this.server.close();
Logger.info('Health server stopped');
}
}
}
// Main application
let monitor = null;
let healthServer = null;
async function main() {
try {
Logger.info('Starting Ente OTP Email Monitor');
// Validate environment
if (!process.env.CLOUDRON_EMAIL_SMTP_SERVER) {
throw new Error('CLOUDRON_EMAIL_SMTP_SERVER not found. Email addon may not be configured.');
}
// Initialize components
const emailTemplate = new EmailTemplate(CONFIG.EMAIL_TEMPLATES_DIR);
const emailSender = new EmailSender(CONFIG);
// Test email connectivity
Logger.info('Testing email connectivity...');
await emailSender.transporter.verify();
Logger.info('Email connectivity verified');
// Start log monitor
monitor = new LogMonitor(CONFIG.LOG_FILE, emailSender, emailTemplate);
monitor.start();
// Start health server
healthServer = new HealthServer();
healthServer.start();
Logger.info('Ente OTP Email Monitor started successfully');
// Handle graceful shutdown
process.on('SIGINT', () => {
Logger.info('Received SIGINT, shutting down gracefully...');
if (monitor) monitor.stop();
if (healthServer) healthServer.stop();
process.exit(0);
});
process.on('SIGTERM', () => {
Logger.info('Received SIGTERM, shutting down gracefully...');
if (monitor) monitor.stop();
if (healthServer) healthServer.stop();
process.exit(0);
});
} catch (err) {
Logger.error('Failed to start OTP Email Monitor:', err.message);
process.exit(1);
}
}
// Start the application
if (require.main === module) {
main();
}
module.exports = {
LogMonitor,
EmailSender,
EmailTemplate,
Logger
};

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "ente-otp-email-monitor",
"version": "1.0.0",
"description": "OTP email monitoring service for Ente Cloudron app",
"main": "otp-email-monitor.js",
"scripts": {
"start": "node otp-email-monitor.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"nodemailer": "^6.9.0"
},
"keywords": [
"ente",
"otp",
"email",
"cloudron",
"monitoring"
],
"author": "Ente Cloudron Integration",
"license": "Apache-2.0"
}

1412
start.sh

File diff suppressed because it is too large Load Diff