replace imap gmail sync with mbsync + maildir import, app password UI
- rewrite gmail-sync handler: mbsync downloads to local Maildir, then import to sqlite - add app password field to google integration config and API - gmail sync section independent from oauth in settings UI - live mbsync progress streaming to job status - recoverable failure email with instructions for overquota/auth errors - sync meta persisted to job on failure for richer notifications Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,64 @@
|
||||
import Layout from 'emailer/emails/layouts/MainLayout.jsx';
|
||||
import { Container, Text } from '@react-email/components';
|
||||
import { Container, Text, Link } from '@react-email/components';
|
||||
|
||||
type JobEmailData = {
|
||||
job: { type: string; error?: string };
|
||||
job: {
|
||||
type: string;
|
||||
error?: string;
|
||||
meta?: {
|
||||
gmailSyncRecoverable?: boolean;
|
||||
gmailSyncEmailCount?: number;
|
||||
gmailSyncIsAuthFail?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const GmailSyncRecovery = ({ job }: JobEmailData) => {
|
||||
const emailCount = job.meta?.gmailSyncEmailCount ?? 0;
|
||||
const isAuthFail = job.meta?.gmailSyncIsAuthFail;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text className="text-base font-semibold text-green-700">Don't worry — your progress is saved.</Text>
|
||||
{emailCount > 0 && (
|
||||
<Text className="text-sm text-gray-700">
|
||||
Your mailbox already has {emailCount.toLocaleString()} emails downloaded. The sync will resume from where it
|
||||
stopped — it won't re-download emails you already have.
|
||||
</Text>
|
||||
)}
|
||||
<Text className="text-sm text-gray-700">
|
||||
{isAuthFail
|
||||
? 'Google temporarily blocked sign-in after hitting bandwidth limits. This usually clears up within a few hours.'
|
||||
: 'Gmail throttled the connection after downloading too much data at once. This is normal for large mailboxes.'}
|
||||
</Text>
|
||||
<Text className="text-sm font-semibold text-gray-800">To resume syncing:</Text>
|
||||
<Text className="text-sm text-gray-700" style={{ marginTop: 0 }}>
|
||||
1. Wait a few hours for Google to lift the block{'\n'}
|
||||
2. Generate a new App Password at{' '}
|
||||
<Link href="https://myaccount.google.com/apppasswords">myaccount.google.com/apppasswords</Link> (Google revokes
|
||||
them after repeated auth failures){'\n'}
|
||||
3. Save the new password in Settings → Integrations → Google Account{'\n'}
|
||||
4. Click "Sync Gmail Inbox" to resume — only new emails will be downloaded
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Email = ({ job }: JobEmailData) => {
|
||||
const isRecoverableGmail = job?.type === 'gmail-sync' && job.meta?.gmailSyncRecoverable;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Container>
|
||||
<Text className="pt-4 text-2xl">Job Failed</Text>
|
||||
<Text>Your {job?.type ?? 'unknown'} job has failed.</Text>
|
||||
{job?.error ? <Text className="text-sm text-gray-600">{job.error}</Text> : null}
|
||||
<Text className="pt-4 text-2xl">{isRecoverableGmail ? 'Gmail Sync Paused' : 'Job Failed'}</Text>
|
||||
{isRecoverableGmail ? (
|
||||
<GmailSyncRecovery job={job} />
|
||||
) : (
|
||||
<>
|
||||
<Text>Your {job?.type ?? 'unknown'} job has failed.</Text>
|
||||
{job?.error ? <Text className="text-sm text-gray-600">{job.error}</Text> : null}
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user