@novacraft-engineering/mailbox 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +110 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/app/api/mail/accessors/route.ts +197 -0
- package/app/api/mail/attachments/route.ts +54 -0
- package/app/api/mail/company-signature/route.ts +36 -0
- package/app/api/mail/contacts/route.ts +13 -0
- package/app/api/mail/emails/[id]/attachments/download/route.ts +69 -0
- package/app/api/mail/emails/[id]/attachments/forward/route.ts +65 -0
- package/app/api/mail/emails/[id]/route.ts +127 -0
- package/app/api/mail/emails/route.ts +116 -0
- package/app/api/mail/events/route.ts +12 -0
- package/app/api/mail/fonts/route.ts +44 -0
- package/app/api/mail/inbox/attachments/download/route.ts +50 -0
- package/app/api/mail/inbox/attachments/forward/route.ts +54 -0
- package/app/api/mail/inbox/attachments/route.ts +99 -0
- package/app/api/mail/inbox/body/route.ts +14 -0
- package/app/api/mail/inbox/counts/route.ts +25 -0
- package/app/api/mail/inbox/route.ts +506 -0
- package/app/api/mail/link-check/route.ts +95 -0
- package/app/api/mail/login/route.ts +38 -0
- package/app/api/mail/logout/route.ts +9 -0
- package/app/api/mail/maintenance/attachments/route.ts +26 -0
- package/app/api/mail/maintenance/bodies/route.ts +30 -0
- package/app/api/mail/maintenance/list-columns/route.ts +25 -0
- package/app/api/mail/maintenance/threads/route.ts +32 -0
- package/app/api/mail/me/route.ts +13 -0
- package/app/api/mail/outgoing-upload/route.ts +37 -0
- package/app/api/mail/password/route.ts +64 -0
- package/app/api/mail/pixel/[id]/route.ts +20 -0
- package/app/api/mail/push/route.ts +42 -0
- package/app/api/mail/render-template/route.ts +58 -0
- package/app/api/mail/request-reset/route.ts +65 -0
- package/app/api/mail/reset/route.ts +46 -0
- package/app/api/mail/send/route.ts +205 -0
- package/app/api/mail/settings/route.ts +35 -0
- package/app/api/mail/share/attachment/route.ts +91 -0
- package/app/api/mail/share/route.ts +130 -0
- package/app/api/mail/signature-logo/route.ts +67 -0
- package/app/api/mail/stash/route.ts +55 -0
- package/app/api/mail/threads/route.ts +18 -0
- package/app/api/mail/upload/route.ts +43 -0
- package/app/api/share/[id]/route.ts +85 -0
- package/app/apple-icon.png +0 -0
- package/app/brand/[file]/route.ts +37 -0
- package/app/globals.css +14 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +30 -0
- package/app/mail/AccessCheck.tsx +45 -0
- package/app/mail/AttachmentLightbox.tsx +241 -0
- package/app/mail/ConfirmDialog.tsx +88 -0
- package/app/mail/MailSelect.tsx +138 -0
- package/app/mail/RichEditor.tsx +859 -0
- package/app/mail/layout.tsx +20 -0
- package/app/mail/page.module.css +6320 -0
- package/app/mail/page.tsx +7971 -0
- package/app/mail/pwa.ts +191 -0
- package/app/mail/reset/page.tsx +132 -0
- package/app/mail/search.ts +172 -0
- package/app/manifest.ts +21 -0
- package/app/page.tsx +5 -0
- package/app/robots.ts +22 -0
- package/app/share/[id]/page.tsx +166 -0
- package/app/share/[id]/share.module.css +148 -0
- package/eslint.config.mjs +9 -0
- package/lib/accent-ramp.ts +54 -0
- package/lib/attachments.ts +52 -0
- package/lib/brand.client.ts +52 -0
- package/lib/brand.ts +87 -0
- package/lib/d1.ts +64 -0
- package/lib/default-signature.ts +68 -0
- package/lib/dev-auth.ts +222 -0
- package/lib/email-html.test.ts +74 -0
- package/lib/email-html.ts +169 -0
- package/lib/emails/index.ts +281 -0
- package/lib/emails/templates/academy-followup.html +68 -0
- package/lib/emails/templates/auto-reply.html +33 -0
- package/lib/emails/templates/contact-followup.html +58 -0
- package/lib/emails/templates/field-row.html +4 -0
- package/lib/emails/templates/notification.html +23 -0
- package/lib/fonts.ts +23 -0
- package/lib/link-safety.ts +81 -0
- package/lib/mail-provider.ts +183 -0
- package/lib/mailbox.ts +1888 -0
- package/lib/password.ts +71 -0
- package/lib/public-url.ts +14 -0
- package/lib/push.ts +37 -0
- package/lib/r2.ts +157 -0
- package/lib/rate-limit.ts +43 -0
- package/lib/session.test.ts +43 -0
- package/lib/session.ts +80 -0
- package/lib/signature.ts +28 -0
- package/lib/threads.test.ts +96 -0
- package/lib/threads.ts +72 -0
- package/lib/turso.ts +53 -0
- package/next.config.ts +51 -0
- package/package.json +69 -0
- package/public/brand/README.md +35 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/icon-maskable-512.png +0 -0
- package/public/sw.js +41 -0
- package/scripts/backfill-attachments.mjs +96 -0
- package/scripts/backfill-content-ids.mjs +101 -0
- package/scripts/backfill-list-columns.mjs +80 -0
- package/scripts/offload-bodies.mjs +152 -0
- package/scripts/sample-files.mjs +119 -0
- package/scripts/seed-dev.mjs +240 -0
- package/tools/README.md +18 -0
- package/tools/db-backup.py +158 -0
- package/tools/doh.py +46 -0
- package/tools/import-mbox.py +703 -0
- package/tools/import-takeout.sh +49 -0
- package/tsconfig.json +35 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Import one Google Takeout zip for one mailbox, streaming the mbox out of the zip.
|
|
3
|
+
# Phase 1 (bodies, metadata) runs at once; phase 2 (attachments to the bucket) waits until
|
|
4
|
+
# no other backfill is uploading, so archives never compete for the uplink.
|
|
5
|
+
# Idempotent: re-running resumes; each phase retries until its completion line appears.
|
|
6
|
+
set -u
|
|
7
|
+
ZIP=$1; OWNER=$2
|
|
8
|
+
S=${IMPORT_LOG_DIR:?set IMPORT_LOG_DIR to a scratch directory}
|
|
9
|
+
cd "$(dirname "$0")/.." || exit 1
|
|
10
|
+
set -a; . ./.env.local; set +a
|
|
11
|
+
TAG=${OWNER%%@*}
|
|
12
|
+
ENTRY=$(unzip -l "$ZIP" 2>/dev/null | grep -o "Takeout/Mail/.*\.mbox" | head -1)
|
|
13
|
+
[ -z "$ENTRY" ] && { echo "no mbox inside $ZIP"; exit 2; }
|
|
14
|
+
IMPORTER="tools/import-mbo""x.py"
|
|
15
|
+
ARCHIVE_ID=$(printf "%s" "$ZIP" | shasum | cut -c1-8)
|
|
16
|
+
# Logs are per archive, not per owner: two archives for one mailbox must not share state.
|
|
17
|
+
LOG1="$S/import-$TAG-$ARCHIVE_ID-phase1.log"; LOG2="$S/import-$TAG-$ARCHIVE_ID-attach.log"
|
|
18
|
+
# The first archive imported for a mailbox predates the archive id; keep its history.
|
|
19
|
+
[ -f "$S/import-$TAG-phase1.log" ] && [ ! -f "$LOG1" ] && [ -n "${IMPORT_LEGACY_TAG:-}" ] && [ "$TAG" = "$IMPORT_LEGACY_TAG" ] && cp "$S/import-$TAG-phase1.log" "$LOG1"
|
|
20
|
+
|
|
21
|
+
attempt=0
|
|
22
|
+
until grep -q "^imported for" "$LOG1" 2>/dev/null; do
|
|
23
|
+
attempt=$((attempt + 1)); [ $attempt -gt 8 ] && { echo "phase 1 gave up after 8 attempts: $TAG"; exit 3; }
|
|
24
|
+
echo "[$(date +%H:%M)] phase 1 attempt $attempt: $TAG"
|
|
25
|
+
unzip -p "$ZIP" "$ENTRY" | python3 "$IMPORTER" - --owner "$OWNER" --skip-attachments --writers 3 > "$LOG1" 2>&1
|
|
26
|
+
grep -q "^imported for" "$LOG1" || sleep 120
|
|
27
|
+
done
|
|
28
|
+
echo "[$(date +%H:%M)] PHASE 1 DONE: $TAG — $(grep -A1 '^imported for' "$LOG1" | tail -1 | sed 's/^ *//')"
|
|
29
|
+
|
|
30
|
+
# Phase 2 runs detached in its own lane: one upload at a time across all archives (lock dir),
|
|
31
|
+
# so the queue can move on to the next archive's phase 1 while this one waits its turn.
|
|
32
|
+
phase2() {
|
|
33
|
+
LOCK="$S/attach.lock"
|
|
34
|
+
until mkdir "$LOCK" 2>/dev/null; do sleep 120; done
|
|
35
|
+
trap 'rmdir "$LOCK" 2>/dev/null' EXIT
|
|
36
|
+
while [ "$(ps -eo command | grep "[P]ython.*import-mbox" | grep -c -- "--attach-only")" -ge 1 ]; do sleep 300; done
|
|
37
|
+
attempt=0
|
|
38
|
+
until grep -q "^attachments backfilled" "$LOG2" 2>/dev/null; do
|
|
39
|
+
attempt=$((attempt + 1)); [ $attempt -gt 20 ] && { echo "[$(date +%H:%M)] phase 2 gave up after 20 attempts: $TAG"; exit 4; }
|
|
40
|
+
echo "[$(date +%H:%M)] phase 2 attempt $attempt: $TAG"
|
|
41
|
+
unzip -p "$ZIP" "$ENTRY" | python3 "$IMPORTER" - --owner "$OWNER" --attach-only --store s3 --replace-blob --workers 16 --chunks 6 --max-mbps 4.5 > "$LOG2" 2>&1
|
|
42
|
+
grep -q "^attachments backfilled" "$LOG2" || sleep 120
|
|
43
|
+
done
|
|
44
|
+
echo "[$(date +%H:%M)] PHASE 2 DONE: $TAG — $(grep -A1 '^attachments backfilled' "$LOG2" | tail -1 | sed 's/^ *//')"
|
|
45
|
+
}
|
|
46
|
+
if grep -q "^attachments backfilled" "$LOG2" 2>/dev/null; then echo "[$(date +%H:%M)] phase 2 already complete: $TAG"; exit 0; fi
|
|
47
|
+
if ps -eo command | grep "[P]ython.*import-mbox" | grep -- "--attach-only" | grep -q "$OWNER"; then echo "[$(date +%H:%M)] phase 2 already running: $TAG"; exit 0; fi
|
|
48
|
+
perl -MPOSIX -e 'POSIX::setsid(); exec @ARGV' -- bash -c "$(declare -f phase2); S='$S'; LOG2='$LOG2'; TAG='$TAG'; ZIP='$ZIP'; ENTRY='$ENTRY'; IMPORTER='$IMPORTER'; OWNER='$OWNER'; cd '$PWD'; set -a; . ./.env.local; set +a; phase2" >> "$S/takeout-queue.log" 2>&1 < /dev/null &
|
|
49
|
+
echo "[$(date +%H:%M)] phase 2 queued (detached, waits for the upload lane): $TAG"
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"incremental": true,
|
|
17
|
+
"plugins": [
|
|
18
|
+
{
|
|
19
|
+
"name": "next"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"paths": {
|
|
23
|
+
"@/*": ["./*"]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"next-env.d.ts",
|
|
28
|
+
"**/*.ts",
|
|
29
|
+
"**/*.tsx",
|
|
30
|
+
".next/types/**/*.ts",
|
|
31
|
+
".next/dev/types/**/*.ts",
|
|
32
|
+
"**/*.mts"
|
|
33
|
+
],
|
|
34
|
+
"exclude": ["node_modules"]
|
|
35
|
+
}
|