Repository navigation
Releases: GortexAI/franco
Releases · GortexAI/franco
Release list
Franco 0.5.1
Security
- Bounces are verified before Franco acts on them. Anyone can send a message that looks like a bounce, and acting on one marks a contact's address invalid and moves the bounce rate toward the kill switch. A delivery report is acted on only when it references the Message-ID of an email Franco actually sent (
In-Reply-ToorReferences) and names that email's recipient. A report that matches only by naming an address Franco emailed in the last 14 days could be forged by anyone who knows whom Franco emails, so it is logged asbounce_needs_reviewfor a person to check, and changes nothing. Unmatched reports are logged asbounce_ignored. - Temporary delivery delays ("delayed",
4.x.xstatuses) are no longer treated as hard bounces, and the same bounce is never counted twice.
Changed
- The README's dashboard GIF shows the current Outbox: approved-copy campaigns in preview, sign-off cards, and each draft's campaign, owner, sourced observation and compliance footer.
Franco 0.5.0
Added
pip install franco. Franco is on PyPI and works without a checkout:franco init [DIR]creates a home folder:franco.yaml, a private.env, editableprompts/andskills/,data/and a.gitignorethat keeps credentials and contact data out of git. It never overwrites existing files.- Franco finds its home as
$FRANCO_HOME, else the source checkout (unchanged for clones and Docker), else the current folder when it has afranco.yaml, else~/.franco. - The wheel bundles the default prompts, skills and templates. A file in the home folder wins; delete it to fall back to the shipped version.
- A pip-installed Franco never writes into its installed package.
franco installonly fetches Chromium when Franco was installed from PyPI.
- Releases publish to PyPI with trusted publishing (no stored token) and PEP 740 attestations, before the GitHub Release is created.
Fixed
franco traincreatedskills/files without creating the folder first, which failed in a fresh home folder.- The dashboard's setup check counted only a
.venvinside the project as a virtual environment.
Franco 0.4.0
Franco is now open source under GortexAI/franco, published as a container image, and released automatically.
Upgrading from 0.3.0
- Set your postal address. The template
franco.yamlnow ships withcompliance.require_footer: trueand a blankpostal_address. No cold email sends until you enter your own address. If your config predates this, the footer stays off until you turn it on. - The dashboard only answers on localhost. If you reach it under another hostname (from Docker, for example), list it in
FRANCO_DASHBOARD_HOSTS. - New config sections (
outreach,compliance) all have defaults. Existing configs load unchanged.
Added
- Approved-copy outreach (
outreach.mode: templates):- Signed-off sequences in
outreach.campaignsare sent word for word to researched accounts, with no model involved. - Merge fields are limited to
{{first_name}},{{last_name}},{{company}},{{title}},{{sender_name}}and{{verified_observation}}. The observation is a sourced, dated fact recorded per account. An account with an unfilled field is skipped, never sent with a gap. - Steps are scheduled in business days in
outreach.timezone, and each follow-up is re-timed from the day the previous step actually went out.
- Signed-off sequences in
- Raad mode (
outreach.mode: raad), named after Raad Mobrem, CEO of Intro, whose outbound approach it follows:- Up to three
outreach.benefits; every funnel names exactly one. outreach.funnelsare narrow segments matched on observed signals (TECH_STACK=AdSense|Prebid) or account fields (segment=marketplace), tried narrowest first.- Email 1 is written from the funnel's story, the account's sourced facts, the one benefit and a role-specific angle, then the funnel's approved follow-ups are queued.
franco funnels(and the dashboard's Campaigns tab) shows how companies split across funnels;--company DOMAINexplains one placement.- The profiler now detects publisher ad tags (Google AdSense, Ad Manager, Prebid, Amazon Publisher Services).
- Up to three
- Bring your own lists as CSV files.
franco import FILE --kind accounts|contacts|suppressions, plus an Import CSV panel on the dashboard's Contacts tab:- Each kind has a template (
--template), header aliases,--dry-runand optional--verify. - Every row is validated before anything is written.
franco suppress importparses real CSV (with areasoncolumn, or one address per line), andexportquotes properly.
- Each kind has a template (
- Preview and sign-off. New approved-copy campaigns and raad funnels start in preview (
outreach.preview_accounts, default 10 per owner):- Preview emails always wait for review, even on autopilot, and nothing sends until someone signs off.
- Commands:
franco preview [KEY] [--csv FILE],franco preview signoff KEY --by NAME,franco preview reopen KEY. The Outbox shows a sign-off card for each campaign in preview.
- Compliance in code:
compliance.require_footerputs the sender, company and postal address plus "Reply "no thanks" to stop." on every cold email in every mode. The gate refuses cold email without the footer, and nothing sends while the postal address is blank.- Cold email gets List-Unsubscribe headers (mailto, plus optional one-click HTTPS).
compliance.blocked_claimsis enforced on every email, replies included.- A shared suppression list covers addresses and whole domains (
franco suppress).
- Account-level outreach:
- One contact per account in a sequence at a time, and one owner per account.
- A reply or opt-out from anyone stops queued email to everyone at that company.
- Follow-ups wait for the previous step, and are cancelled if it was rejected or failed.
- Replies:
- Quoted history and Franco's own footer are removed before any rule runs.
- "no thanks" (body or subject) is an opt-out, and is suppressed before the optional acknowledgement is sent.
- Out-of-office replies no longer end a sequence.
- A new
laterintent pauses the contact and records when to get back in touch. outreach.reply_templatesprovides approved wording per intent;not_interestedsends its template as written.
- Autopilot toggle.
franco autopilot on|off|default(and an Outbox button) overrideschannels.email.require_approvalat runtime; turning it on approves what is waiting. Every gate still applies. - Outbox context and editing. Each draft shows why it exists: campaign, owner, buyer, observation with its source and the date checked. Drafts can be edited before approval with
franco outbox --edit IDorEon the dashboard. - Container image
ghcr.io/gortexai/franco, for linux/amd64 and linux/arm64, with an SBOM and a signed build-provenance attestation:- Pull requests build and smoke-test it.
- Every push to
mainpublishes:edge, and each release publishes:X.Y.Z,:X.Yand:latest. docker-compose.ymluses the published image.
- Release workflow. Tagging
vX.Y.Zchecks the version againstpyproject.tomland the CHANGELOG, builds and tests the sdist and wheel, and creates the GitHub Release. See "Releasing" inCONTRIBUTING.md. - CI tests Python 3.11, 3.12, 3.13 and 3.14 on Linux, macOS and Windows.
SECURITY.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md, issue forms and a pull request template.
Security
- The dashboard refuses cross-site requests and DNS-rebound hosts. Any web page could previously POST to
localhost:5555, for example to turn on autopilot. - Raad-mode drafts are checked in code. Facts reach the model as fenced, untrusted data. Code rejects any draft that contains a blocked claim, HTML, an email address, an IP address, or any link or domain other than the funnel's landing page (Unicode look-alike dots included).
- Dependencies are locked and fully scanned.
requirements.txtis generated bypip-compilefromrequirements.in, with every package pinned and hashed.- Dependabot alerts now cover all 28 packages, up from 12.
- Dependabot security updates work again; they had been failing without a lockfile.
- Renovate recompiles the lock and keeps
pyproject.toml,requirements.inand the lock in step.tests/test_dependencies.pychecks that they agree.
- Every GitHub Action is pinned to a full commit SHA, with Renovate keeping the pins current, so a moved or hijacked tag can't change what runs with publishing and signing permissions.
- Fixed all 32 CodeQL alerts inherited from the original code:
- Path injection (4, high): the dashboard serves only files listed under
franco/web/, never a requested path. - Clear-text logging of secrets (6, high): the setup wizard reads API keys and passwords through a separate hidden-input function. Secrets were only ever written to
.env. - Bad HTML tag filter (1, high): script and style stripping handles end tags like
</script >. - Stack-trace exposure (20, medium): dashboard endpoints and run reports log the details and show fixed wording. Franco's own explanations, such as "OpenStreetMap has no category for 'widgets'", are still shown as written.
- Log injection (1, medium): newlines are removed from logged search terms, provider names and errors.
- Path injection (4, high): the dashboard serves only files listed under
- CSV exports (prospects, preview, suppressions) neutralise cells starting with
=,+,-or@, so imported or scraped text can't run as a spreadsheet formula. - Scraped website and LinkedIn URLs are linked only when they are http(s).
- Secret scanning with push protection, private vulnerability reporting, CodeQL and Code Quality are enabled on the repository.
Fixed
- The Docker image never had the Claude CLI. Its installer is a bash script but ran under
sh, and|| truehid the failure, so containers couldn't make any model call. It now runs with bash, and the build fails ifclaudeis missing. - A
.dockerignorekeeps.env,franco.local.yaml,data/and the virtualenv out of local Docker builds. - When several steps of a sequence were overdue at once, they no longer all send in the same cycle.
- Accounts staged on a weekend start on Monday, not Tuesday.
- No empty campaign rows when every account in a batch is skipped.
- A usage test no longer fails between 5pm and midnight Pacific (it seeded data with the UTC date while Franco uses the local one).