This week we asked Perplexity a simple question: "what is Cresca email?"
Cresca is our product, an email platform for marketing campaigns and transactional email. We wanted to know what a stranger would be told about it.
The answer was accurate for about four lines. Then it said we support "automation workflows".
We don't. Automation is on the roadmap, and our own site says "launching soon". So an AI assistant was telling buyers about a feature we haven't shipped, and we had no idea where it got that.
This post covers how we traced it, what else we found, the code we wrote so it doesn't happen again, and what is still broken.
The text of an AI answer is the least useful part. The source list is where the work is.
Perplexity showed ten sources for that one answer:
| Source | Count |
|---|---|
Pages on our own site (homepage, docs quickstart, llms.txt , privacy, contact, app login) |
6 |
| A guide our founder published on dev.to | 1 |
| A different company that shares our name | 2 |
| Our BetaList listing | 1 |
Nine of those said nothing about automation. The tenth, BetaList, did.
That listing went live in October 2025. Nobody on the team had opened it since. It said:
So the model didn't invent anything. It read a page we wrote a year ago, and gave it the same weight as our homepage.
If one old listing was wrong, others would be too. We have profiles on G2, Capterra, GetApp and Software Advice. They all run through one seller portal now, and the descriptions had been prefilled automatically from an older version of our site.
Here is every wrong fact we found, and the page it came from:
| Wrong claim | Where | What's true |
|---|---|---|
| "automation workflows" | Capterra, GetApp, Software Advice, BetaList | Not shipped yet |
| "automatic configuration on Cloudflare" | GetApp | You add the DNS records yourself |
| "support via email, live chat…" | Software Advice | Email only |
| "$29 per user, per month" | G2, Capterra | $29 flat, per account |
| "Free trial not available" | G2, Capterra | 30-day free plan, no card |
| "50 free emails" | BetaList | 1,000 emails |
Six wrong facts across five sites. Every one of those sites has more authority than our own domain. Google's AI Overview had picked up the automation claim as well, and described us as "automating" emails.
None of this appeared in any SEO audit we'd run, because an audit looks at your site. Our site was right. Everything around it was stale.
The "per user" one is worth a second look. A buyer with a five-person team reads "$29 per user" and works out $145 a month. The real price is $29. We'd have lost that buyer without ever knowing they looked.
The root cause wasn't any single listing. It was that the facts about our product lived in a dozen places, each written by hand at a different time.
So we wrote them down once, as data. Two lists: things no page about us should say, and things every page should say.
{
"listings": [
"https://betalist.com/startups/cresca",
"https://www.capterra.com/p/10280784/Cresca/",
"https://www.getapp.com/all-software/a/cresca/",
"https://www.softwareadvice.com/product/788111-Cresca/",
"https://www.g2.com/products/cresca/pricing",
"https://www.saashub.com/cresca-xyz"
],
"mustNotSay": [
{
"pattern": "automation workflows|marketing automation|automated workflows|campaign automation",
"why": "automation is not shipped yet",
"okNear": "launching soon|coming soon|not available yet"
},
{ "pattern": "per user", "why": "pricing is a flat monthly price" },
{ "pattern": "live chat", "why": "support is by email" },
{ "pattern": "cloudflare", "why": "DNS records are added by hand" },
{ "pattern": "50 free emails|7-day free trial", "why": "old free plan" }
],
"shouldSay": [
{ "pattern": "30[- ]day", "why": "the free plan lasts 30 days" },
{ "pattern": "transactional", "why": "half the product" }
],
"ignore": ["Accounting & Finance Marketing Automation Sales Tools"]
}
When automation ships, we delete one entry. When the price changes, we change one line, and the script below tells us which pages are now out of date.
This is plain Node 22. No dependencies, because fetch is built in.
// Usage: node check.mjs [facts.json]
// Fetches every listing and reports claims that contradict the facts file.
import { readFile } from "node:fs/promises";
const facts = JSON.parse(await readFile(process.argv[2] ?? "facts.json", "utf8"));
const UA = "Mozilla/5.0 (compatible; listing-check/1.0)";
function toText(html) {
return html
.replace(/<(script|style)[\s\S]*?<\/\1>/gi, " ")
.replace(/<[^>]+>/g, " ")
.replace(/&/g, "&")
.replace(/'|’/g, "'")
.replace(/\s+/g, " ");
}
function snippet(text, index, length) {
return text.slice(Math.max(0, index - 50), index + length + 50).trim();
}
let problems = 0;
for (const url of facts.listings) {
const host = new URL(url).hostname.replace(/^www\./, "");
let res;
try {
res = await fetch(url, { headers: { "user-agent": UA }, redirect: "follow" });
} catch (err) {
console.log(`\n${host}\n SKIP could not fetch (${err.cause?.code ?? err.message})`);
continue;
}
let text = toText(await res.text());
// Site navigation and category menus can contain your banned words.
for (const phrase of facts.ignore ?? []) text = text.replaceAll(phrase, " ");
// A bot wall returns a short page with none of your content on it.
if (!res.ok || text.length < 2000) {
console.log(`\n${host}\n SKIP HTTP ${res.status}, ${text.length} chars (blocked? check by hand)`);
continue;
}
console.log(`\n${host}`);
let clean = true;
for (const { pattern, why, okNear } of facts.mustNotSay) {
const m = new RegExp(pattern, "i").exec(text);
// "Automation workflows are launching soon" is true, so let it pass.
const excused = m && okNear && new RegExp(okNear, "i").test(snippet(text, m.index, m[0].length));
if (m && !excused) {
clean = false;
problems++;
console.log(` WRONG "${m[0]}" (${why})`);
console.log(` …${snippet(text, m.index, m[0].length)}…`);
}
}
for (const { pattern, why } of facts.shouldSay) {
if (!new RegExp(pattern, "i").test(text)) {
clean = false;
problems++;
console.log(` MISSING /${pattern}/ (${why})`);
}
}
if (clean) console.log(" OK");
}
console.log(`\n${problems} problem(s) found.`);
process.exit(problems ? 1 : 0);
It exits with code 1 when something is wrong, so it can run in CI or on a weekly schedule.
betalist.com
WRONG "Automation workflows" (automation is not shipped yet)
…import, tags and campaign reporting are built in. Automation workflows are launching soon. Free for 30 days with no cred…
capterra.com
SKIP HTTP 403, 1025 chars (blocked? check by hand)
getapp.com
SKIP HTTP 403, 1025 chars (blocked? check by hand)
softwareadvice.com
SKIP HTTP 403, 999 chars (blocked? check by hand)
g2.com
SKIP HTTP 403, 52 chars (blocked? check by hand)
saashub.com
WRONG "Marketing Automation" (automation is not shipped yet)
…ng Lead Generation Help Desk Accounting & Finance Marketing Automation Sales Tools File Manager Sales Workflow Contact M…
WRONG "Cloudflare" (DNS records are added by hand)
…with SPF and DKIM (records added automatically on Cloudflare, existing ones untouched) Bounces and complaints…
3 problem(s) found.
That output taught us three things.
Two of the three hits were false positives. BetaList says automation is "launching soon", which is true. SaaSHub has "Marketing Automation" in its category menu, which has nothing to do with us. That's why the facts file has okNear (skip a match if an honest qualifier sits next to it) and ignore (strip known menu text first).
The third was real, and we'd missed it by hand. Our SaaSHub listing still says DNS records are "added automatically on Cloudflare". We had spent a morning fixing five sites and never opened that one. The script found it in four seconds.
Most review sites block scripts. G2, Capterra, GetApp and Software Advice all returned a 403 on the first run. Software Advice let the second run through; the other three never did. The script says so instead of reporting "OK", because a blocked page that passes silently is worse than no check. Those we still open by hand.
After adding the two rules, the second run (the three blocked sites are left out here):
betalist.com
OK
softwareadvice.com
OK
saashub.com
WRONG "Cloudflare" (DNS records are added by hand)
1 problem(s) found.
Six of the ten sources were our own pages, so they need to state the facts plainly, in text a model can read without running JavaScript.
llms.txt. A plain-text summary at the site root. Ours says what isn't available as clearly as what is:
> Cresca is an AI email marketing and transactional email platform for teams of every size.
Cresca (cresca.xyz) is AI email marketing and transactional email software, founded in 2025.
It is not affiliated with other companies or artists named Cresca.
## What Cresca does
- Campaigns: send now or schedule campaigns. Email automation workflows (welcome series,
follow-ups, re-engagement) are launching soon and are not available yet.
- Reporting: recipients, deliveries, bounces, complaints and unsubscribes for every campaign.
A/B testing is not offered.
## Pricing (USD, billed monthly)
- Free: $0 for 30 days, 250 contacts, 1,000 emails, 50 campaigns, 20 AI emails.
- Professional: $29/month, 5,000 contacts, 7,500 emails/month.
Perplexity cited this file directly. Google says it doesn't use llms.txt, so treat it as helpful for some engines, not all.
robots.txt. Search and answer crawlers are named and allowed:
User-agent: *
User-agent: OAI-SearchBot
User-agent: ChatGPT-User
User-agent: Claude-SearchBot
User-agent: PerplexityBot
Allow: /
Sitemap: https://www.cresca.xyz/sitemap.xml
Organization schema. Two other companies share our name, and two of the ten sources above belonged to one of them. disambiguatingDescription exists for exactly this:
export function generateOrganizationSchema() {
return {
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${SITE.url}/#organization`,
name: SITE.name,
alternateName: ['Cresca.xyz', 'Cresca email'],
url: SITE.url,
disambiguatingDescription:
'Cresca (cresca.xyz) is AI email marketing and transactional email software, ' +
'founded in 2025. It is not affiliated with other companies or artists named Cresca.',
foundingDate: '2025',
// Only profiles that are actually owned and public.
sameAs: SITE.sameAs,
};
}
It hasn't fully worked. Perplexity still treats the other company as the main result for the bare name. Google's AI Overview for "cresca" listed us as one of the meanings on November 11. On November 12 it listed six meanings, and we weren't one of them.
Google's AI Overview for our bare name on November 12. Six meanings, none of them us. The day before, we were on the list.
So treat one good answer as a sample, not a result. These answers change from day to day.
Check AI answers logged out. We tried ChatGPT first, signed in. It called our product "an interesting opportunity", because the account's memory already knew who was asking. That is not what a buyer sees. Use a private window, or an engine that works without an account.
Forms fail quietly. Capterra rejected our corrected description. The only sign was a toast in the corner for a few seconds: "must not contain all-caps words". It didn't like CSV and REST. An earlier paste that day had failed the same way and nobody noticed. After you save, reload the form and read it back.
One dropdown, four sites. The pricing form has a "pricing model" menu. Ours was saved as "Per User" when it should have been "Flat Rate". One portal feeds four review sites, so the wrong model showed up on all four at once.
The GetApp pricing table after the fix: three flat monthly prices, free trial ticked, free plan crossed.
Every listing we can edit now matches the site, apart from the SaaSHub line the script caught. The changes went live on the public pages within about an hour.
A day later we asked Perplexity the same question again. The automation line was gone.
The same question, a day after the listings were corrected. Campaigns, the API, contacts, own-domain sending, the plans. No automation.
One answer doesn't prove the fix caused it, since these answers vary from run to run. We'll ask the same set of questions again on November 25 and compare.
And there's a harder problem that a script won't fix. We also asked ten category questions across Perplexity and Google's AI Mode: "best transactional email API", "Amazon SES alternatives", and so on.
Cresca was named in none of them.
For the SES question, Perplexity even cited a comparison post our founder wrote on dev.to. It used the post as a source, then recommended nine other products.
So AI search now describes us correctly if you already know our name, and doesn't mention us if you don't. The first half was a cleanup job. The second half means earning a place on the lists and review sites those answers draw from, and that takes longer.
We'd still rather be missing from an answer than appear in it with a feature we never shipped.
Cresca is an email platform for marketing campaigns and transactional email: cresca.xyz. If you run this check on your own product, tell us what it got wrong.