gmail sync: don't retry on overquota, import what we have
- OVERQUOTA from Gmail now continues to import step instead of retrying - partial sync completion email tells user to try again in a few hours - engine syncs handler meta to job on completion for richer notifications Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -218,6 +218,7 @@ async function runJob(job: Job) {
|
|||||||
if (final && final.status === 'running') {
|
if (final && final.status === 'running') {
|
||||||
final.status = 'completed';
|
final.status = 'completed';
|
||||||
final.completedAt = Date.now();
|
final.completedAt = Date.now();
|
||||||
|
final.meta = { ...final.meta, ...sharedMeta };
|
||||||
await writeJob(final);
|
await writeJob(final);
|
||||||
console.log(`[queue] Job ${final.id} completed`);
|
console.log(`[queue] Job ${final.id} completed`);
|
||||||
if (final.notify !== false) await notifyCompletion(final);
|
if (final.notify !== false) await notifyCompletion(final);
|
||||||
|
|||||||
@@ -268,19 +268,29 @@ const gmailSyncHandler: JobHandler = {
|
|||||||
await readLoop;
|
await readLoop;
|
||||||
|
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
console.error(`[gmail-sync] mbsync failed`);
|
|
||||||
const isOverquota = stderrBuf.includes('OVERQUOTA');
|
const isOverquota = stderrBuf.includes('OVERQUOTA');
|
||||||
const isAuthFail = stderrBuf.includes('AUTHENTICATIONFAILED') || stderrBuf.includes('Invalid credentials');
|
const isAuthFail = stderrBuf.includes('AUTHENTICATIONFAILED') || stderrBuf.includes('Invalid credentials');
|
||||||
if (isOverquota || isAuthFail) {
|
|
||||||
|
if (isOverquota) {
|
||||||
|
// Gmail throttled us — don't retry, just import what we have
|
||||||
|
const emailCount = await countMaildirFiles(maildirPath);
|
||||||
|
console.log(`[gmail-sync] Gmail OVERQUOTA — proceeding to import ${emailCount} downloaded emails`);
|
||||||
|
ctx.meta.gmailSyncPartial = true;
|
||||||
|
ctx.meta.gmailSyncEmailCount = emailCount;
|
||||||
|
// Fall through to import step
|
||||||
|
} else {
|
||||||
|
console.error(`[gmail-sync] mbsync failed`);
|
||||||
|
if (isAuthFail) {
|
||||||
const emailCount = await countMaildirFiles(maildirPath);
|
const emailCount = await countMaildirFiles(maildirPath);
|
||||||
ctx.meta.gmailSyncRecoverable = true;
|
ctx.meta.gmailSyncRecoverable = true;
|
||||||
ctx.meta.gmailSyncEmailCount = emailCount;
|
ctx.meta.gmailSyncEmailCount = emailCount;
|
||||||
ctx.meta.gmailSyncIsAuthFail = isAuthFail;
|
ctx.meta.gmailSyncIsAuthFail = true;
|
||||||
}
|
}
|
||||||
throw new Error(`mbsync exited with code ${exitCode}: ${stderrBuf.slice(-500)}`);
|
throw new Error(`mbsync exited with code ${exitCode}: ${stderrBuf.slice(-500)}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
console.log('[gmail-sync] mbsync completed successfully');
|
console.log('[gmail-sync] mbsync completed successfully');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Always clean up config (contains password)
|
// Always clean up config (contains password)
|
||||||
await unlink(configPath).catch(() => {});
|
await unlink(configPath).catch(() => {});
|
||||||
|
|||||||
@@ -2,19 +2,39 @@ import Layout from 'emailer/emails/layouts/MainLayout.jsx';
|
|||||||
import { Container, Text } from '@react-email/components';
|
import { Container, Text } from '@react-email/components';
|
||||||
|
|
||||||
type JobEmailData = {
|
type JobEmailData = {
|
||||||
job: { type: string; completedAt?: number };
|
job: { type: string; completedAt?: number; meta?: { gmailSyncPartial?: boolean; gmailSyncEmailCount?: number } };
|
||||||
};
|
};
|
||||||
|
|
||||||
const Email = ({ job }: JobEmailData) => {
|
const Email = ({ job }: JobEmailData) => {
|
||||||
|
const isPartialGmail = job?.type === 'gmail-sync' && job.meta?.gmailSyncPartial;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Container>
|
<Container>
|
||||||
<Text className="pt-4 text-2xl">Job Completed</Text>
|
<Text className="pt-4 text-2xl">{isPartialGmail ? 'Gmail Sync Paused' : 'Job Completed'}</Text>
|
||||||
|
{job?.type === 'gmail-sync' ? (
|
||||||
|
isPartialGmail ? (
|
||||||
|
<>
|
||||||
<Text>
|
<Text>
|
||||||
{job?.type === 'gmail-sync'
|
Gmail throttled the connection after downloading too many emails at once. This is normal for large
|
||||||
? 'Your Google account is now completely synced.'
|
mailboxes — your emails have been imported successfully.
|
||||||
: `Your ${job?.type ?? 'unknown'} job has finished successfully.`}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
{job.meta?.gmailSyncEmailCount ? (
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
{job.meta.gmailSyncEmailCount.toLocaleString()} emails downloaded so far.
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
Wait a few hours and run the sync again to continue downloading the rest. It will pick up where it left
|
||||||
|
off.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Text>Your Google account is now completely synced.</Text>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Text>Your {job?.type ?? 'unknown'} job has finished successfully.</Text>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user