Skip email generation for no-email contacts, make Stop generation more robust
Seth+1: Claude Sonnet 5
2d73657
Contacts with no email address (LinkedIn-only prospects) were being run
through generate_email_sequence anyway, which always raised and got
reported as a "failed" contact needing retry — even though retrying can
never succeed without an email on file. Now skipped cleanly with no
error, leaving LinkedIn generation for them unaffected.
Stop generation's cancel request had no failure handling, so a dropped
or errored POST left the button stuck on "Stopping…" with generation
still running server-side. Added a retry + surfaced error, plus a
"Finishing current contact…" label after a few seconds so a genuinely
in-progress stop (bounded by the current contact's in-flight GPT call)
doesn't read as frozen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Turn campaign timezone field into a searchable city/country picker
Seth+1: Claude Sonnet 5
0d1ff1d
Free-text timezone entry let users type non-IANA names (e.g. "India")
that silently failed open and disabled the business-hours restriction
with no feedback. Replaced it with a type-ahead combobox backed by a
curated city/country list plus the full IANA zone list as fallback, so
only a real timezone id can be committed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LinkedIn connects/DMs are now throttled per connected account against
conservative daily/weekly caps (rolling windows, randomized jitter) enforced
by a new background scheduler so delivery keeps progressing without a
browser tab open. Email sending gets per-campaign timezone/business-hours/
interval controls set during campaign setup. Content generation can now be
stopped mid-run and resumed later without restarting.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Allow LinkedIn generation for contacts with no email but a LinkedIn URL
Seth+1: Claude Sonnet 5
56eda2f
Apollo CSV exports frequently can't verify an email but still include a
valid LinkedIn profile URL — generate_linkedin_sequence() was hard-
requiring an email and failing these contacts entirely, even though
LinkedIn sending never uses email (it already resolves the profile from
the LinkedIn URL at send time). Also fixes contact field extraction
(both email and LinkedIn generation) to recognize Apollo's actual
column names like "Company Name" instead of only "Company", which was
silently leaving company blank for every Apollo-sourced contact.
- gpt_service.py: case-insensitive field lookup with the same alias
list main.py already uses elsewhere; LinkedIn generation now only
requires a name, email, or LinkedIn URL — not specifically an email.
Email generation still requires an email (needed to actually send).
- Give generate_email_sequence the same typed generation_failed marker
treatment as LinkedIn instead of silently returning fake fallback
email content on error.
- main.p…
Fix LinkedIn campaign generation reliability, quality, and persistence
Seth+1: Claude Sonnet 5
810c33d
- Stop silently swallowing generation errors as fake generic fallback
content; retry once on transient OpenAI failures, then surface a
typed failure instead of writing fabricated sequence rows.
- Fix step-order/message-numbering bugs so LinkedIn steps keep the
wizard's configured order and messages aren't mislabeled by list
position.
- Enforce the LinkedIn connection-note character cap server-side and
tighten the prompt to require concrete personalization and ban
generic filler openers.
- Give campaigns a real backend record (UploadedFile campaign_name/
status/updated_at + list/detail/meta-patch/delete routes) instead of
browser localStorage, which had a bug where a parse failure would
silently repopulate demo data over real campaigns.
- Reconcile the dashboard from the backend list, auto-save campaign
identity from the wizard on every step, and re-fetch the saved
sequence plan/prompts when reopening a campaign so content and
configuration survive closing and re…