diff --git a/app/api/emails/delete/route.ts b/app/api/emails/delete/route.ts
new file mode 100644
index 0000000..0deb552
--- /dev/null
+++ b/app/api/emails/delete/route.ts
@@ -0,0 +1,39 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { db } from '@/app/db/drizzle';
+import { emails } from '@/app/db/schema';
+import { authenticate, getS3Client } from '@/app/lib/utils';
+import { inArray } from 'drizzle-orm';
+import { DeleteObjectCommand } from '@aws-sdk/client-s3';
+
+export async function POST(req: NextRequest) {
+ if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+
+ const { keys, bucket } = await req.json();
+
+ if (!Array.isArray(keys) || keys.length === 0 || !bucket) {
+ return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 });
+ }
+
+ try {
+ const s3 = getS3Client();
+
+ // Delete from S3
+ const deletePromises = keys.map(key =>
+ s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))
+ .catch(err => {
+ console.error(`Failed to delete ${key} from S3:`, err);
+ return null;
+ })
+ );
+
+ await Promise.all(deletePromises);
+
+ // Delete from database
+ await db.delete(emails).where(inArray(emails.s3Key, keys));
+
+ return NextResponse.json({ success: true, count: keys.length });
+ } catch (error) {
+ console.error('Error deleting emails:', error);
+ return NextResponse.json({ error: 'Failed to delete emails' }, { status: 500 });
+ }
+}
diff --git a/app/api/emails/mark-read/route.ts b/app/api/emails/mark-read/route.ts
new file mode 100644
index 0000000..936b009
--- /dev/null
+++ b/app/api/emails/mark-read/route.ts
@@ -0,0 +1,27 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { db } from '@/app/db/drizzle';
+import { emails } from '@/app/db/schema';
+import { authenticate } from '@/app/lib/utils';
+import { inArray } from 'drizzle-orm';
+
+export async function POST(req: NextRequest) {
+ if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+
+ const { keys } = await req.json();
+
+ if (!Array.isArray(keys) || keys.length === 0) {
+ return NextResponse.json({ error: 'Invalid keys' }, { status: 400 });
+ }
+
+ try {
+ await db
+ .update(emails)
+ .set({ processed: true, processedAt: new Date() })
+ .where(inArray(emails.s3Key, keys));
+
+ return NextResponse.json({ success: true, count: keys.length });
+ } catch (error) {
+ console.error('Error marking emails as read:', error);
+ return NextResponse.json({ error: 'Failed to mark as read' }, { status: 500 });
+ }
+}
diff --git a/app/api/emails/route.ts b/app/api/emails/route.ts
index fcada8d..72b0873 100644
--- a/app/api/emails/route.ts
+++ b/app/api/emails/route.ts
@@ -18,7 +18,11 @@ export async function GET(req: NextRequest) {
const emailList = await db.select({
key: emails.s3Key,
subject: emails.subject,
+ from: emails.from,
+ to: emails.to,
date: emails.date,
+ html: emails.html,
+ raw: emails.raw,
processed: emails.processed,
processedAt: emails.processedAt,
processedBy: emails.processedBy,
@@ -26,14 +30,85 @@ export async function GET(req: NextRequest) {
status: emails.status,
}).from(emails).where(sql`${mailbox} = ANY(${emails.to}) AND ${emails.domainId} = ${domain.id}`);
- return NextResponse.json(emailList.map(e => ({
- key: e.key,
- subject: e.subject,
- date: e.date?.toISOString(),
- processed: e.processed ? 'true' : 'false',
- processedAt: e.processedAt?.toISOString() || null,
- processedBy: e.processedBy,
- queuedTo: e.queuedTo,
- status: e.status,
- })));
+ return NextResponse.json(emailList.map(e => {
+ let preview = '';
+
+ // Check both HTML and raw content for images
+ const htmlContent = e.html || '';
+ const rawContent = e.raw || '';
+
+ // Check for images in HTML
+ const hasImgTags = /
]+>/i.test(htmlContent);
+ const imageCount = (htmlContent.match(/
]+>/gi) || []).length;
+
+ // Check for base64 encoded images
+ const hasBase64Images = /data:image\//i.test(htmlContent);
+
+ // Check for image attachments in raw content
+ const hasImageAttachments = /Content-Type:\s*image\//i.test(rawContent);
+ const attachmentCount = (rawContent.match(/Content-Type:\s*image\//gi) || []).length;
+
+ // Check for multipart/related (usually contains embedded images)
+ const isMultipartRelated = /Content-Type:\s*multipart\/related/i.test(rawContent);
+
+ // Extract text content
+ let textContent = '';
+ if (htmlContent) {
+ textContent = htmlContent
+ .replace(/