Skip to content

Commit cce323b

Browse files
feat(console): route non-sales enterprise inquiries by reason and map compliance fields (#61279)
1 parent 394d49d commit cce323b

5 files changed

Lines changed: 193 additions & 7 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Why a visitor who isn't asking sales reached out, as the Console site's contact form offers it.
2+
export const contactReasons = [
3+
{ value: "account", label: "Get help with my account" },
4+
{ value: "list-models", label: "List my models on OpenCode" },
5+
{ value: "partnership", label: "Partnership, reseller, or MSP" },
6+
{ value: "hackathon-education", label: "Hackathon or education" },
7+
{ value: "other", label: "Something else" },
8+
] as const

‎packages/console/app/src/lib/enterprise.test.ts‎

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { expect, test } from "bun:test"
2-
import { enterpriseEmailContent } from "./enterprise"
1+
import { afterEach, expect, mock, spyOn, test } from "bun:test"
2+
import type { sendEmail } from "./aws"
3+
import { enterpriseEmailContent, handleEnterpriseRequest } from "./enterprise"
4+
5+
afterEach(() => mock.restore())
36

47
const submission = {
58
name: "Fake Visitor",
@@ -26,6 +29,24 @@ test("the sales inbox email escapes submitted HTML", () => {
2629
expect(content.html).toContain("&lt;b&gt;Name&lt;/b&gt;<br>")
2730
})
2831

32+
test("the sales inbox email lists the country and compliance requirements under the spend", () => {
33+
const content = enterpriseEmailContent(
34+
{ ...submission, country: "Germany", inRegionRouting: true, dpa: true, alias: "" },
35+
"$10k+",
36+
)
37+
expect(content.text.split("\n").slice(0, 6)).toEqual([
38+
submission.message,
39+
"",
40+
"Current monthly inference spend: $10k+",
41+
"Country: Germany",
42+
"Compliance requirements: In-region routing (EU or US), Data Processing Agreement (DPA)",
43+
"",
44+
])
45+
expect(content.html).toContain(
46+
"Current monthly inference spend: $10k+<br>Country: Germany<br>Compliance requirements: In-region routing (EU or US), Data Processing Agreement (DPA)<br><br>",
47+
)
48+
})
49+
2950
test("the sales inbox plain-text email is not HTML-escaped", () => {
3051
const content = enterpriseEmailContent(
3152
{ ...submission, name: "Ada <CTO>", message: "Pricing & SSO?", phone: "+1 555 0100", alias: "" },
@@ -49,3 +70,90 @@ test("the sales inbox plain-text email is not HTML-escaped", () => {
4970
].join("\n"),
5071
)
5172
})
73+
74+
const contact = async (fields: Record<string, unknown>) => {
75+
const sent: Parameters<typeof sendEmail>[0][] = []
76+
const leads: Record<string, unknown>[] = []
77+
const response = await handleEnterpriseRequest(
78+
new Request("http://localhost/api/enterprise", {
79+
method: "POST",
80+
headers: { "Content-Type": "application/json" },
81+
body: JSON.stringify({
82+
email: "fake-visitor@example.test",
83+
message: "We'd like to list our models.",
84+
captcha: "token",
85+
...fields,
86+
}),
87+
}),
88+
{
89+
captcha: { secret: "test-secret", fetch: async () => Response.json({ success: true }) },
90+
spam: { apiKey: "test-key", fetch: async () => new Response(null, { status: 503 }) },
91+
salesforceSubmit: async (payload) => {
92+
leads.push(payload)
93+
return true
94+
},
95+
send: async (email) => {
96+
sent.push(email)
97+
},
98+
},
99+
)
100+
return { status: response.status, sent, leads }
101+
}
102+
103+
test("an inquiry that isn't for sales is emailed to the contact inbox, titled with its reason, without a lead", async () => {
104+
spyOn(console, "error").mockImplementation(() => {})
105+
const result = await contact({ reason: "list-models", message: "<b>Our</b> models" })
106+
expect(result.status).toBe(200)
107+
expect(result.leads).toHaveLength(0)
108+
expect(result.sent).toEqual([
109+
{
110+
to: "stefan@anoma.ly",
111+
subject: "List my models on OpenCode",
112+
body: "&lt;b&gt;Our&lt;/b&gt; models<br><br>--<br>fake-visitor@example.test",
113+
text: "<b>Our</b> models\n\n--\nfake-visitor@example.test",
114+
replyTo: "fake-visitor@example.test",
115+
},
116+
])
117+
})
118+
119+
test("an inquiry with an unknown reason or no details is rejected before anything is sent", async () => {
120+
expect(await contact({ reason: "Get help with my account" })).toEqual({ status: 400, sent: [], leads: [] })
121+
expect(await contact({ reason: "account", message: "" })).toEqual({ status: 400, sent: [], leads: [] })
122+
})
123+
124+
test("a contact email that fails to send is reported", async () => {
125+
spyOn(console, "error").mockImplementation(() => {})
126+
const response = await handleEnterpriseRequest(
127+
new Request("http://localhost/api/enterprise", {
128+
method: "POST",
129+
headers: { "Content-Type": "application/json" },
130+
body: JSON.stringify({
131+
email: "fake-visitor@example.test",
132+
message: "Help",
133+
reason: "account",
134+
captcha: "token",
135+
}),
136+
}),
137+
{
138+
captcha: { secret: "test-secret", fetch: async () => Response.json({ success: true }) },
139+
spam: { apiKey: "test-key", fetch: async () => new Response(null, { status: 503 }) },
140+
send: async () => {
141+
throw new Error("SES down")
142+
},
143+
},
144+
)
145+
expect(response.status).toBe(500)
146+
})
147+
148+
test("an inquiry with a malformed country or requirement is rejected before anything is sent", async () => {
149+
expect(await contact({ name: "Ada", role: "CTO", country: "x".repeat(81) })).toEqual({
150+
status: 400,
151+
sent: [],
152+
leads: [],
153+
})
154+
expect(await contact({ name: "Ada", role: "CTO", dpa: "yes" })).toEqual({
155+
status: 400,
156+
sent: [],
157+
leads: [],
158+
})
159+
})

‎packages/console/app/src/lib/enterprise.ts‎

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { i18n } from "~/i18n"
44
import { localeFromRequest } from "~/lib/language"
55
import { createLead } from "~/lib/salesforce"
66
import { inferenceSpendOptions } from "~/lib/inference-spend"
7+
import { contactReasons } from "~/lib/contact-reason"
78
import { ENTERPRISE_SPAM_THRESHOLD, enterpriseSpamProbability } from "~/lib/enterprise-spam"
89
import { verifyHcaptcha } from "~/lib/hcaptcha"
910

@@ -13,13 +14,22 @@ interface EnterpriseFormData {
1314
company?: string
1415
email: string
1516
phone?: string
17+
/** A country name; the Lead's Country is a text field. */
18+
country?: string
1619
inferenceSpend?: string
20+
inRegionRouting?: boolean
21+
dpa?: boolean
22+
/** Set only for an inquiry that isn't for sales; its `contactReasons` value. */
23+
reason?: string
1724
alias?: string
1825
message: string
1926
captcha?: string
2027
}
2128

2229
const EMAIL_OCTOPUS_LIST_ID = "1b381e5e-39bd-11f1-ba4a-cdd4791f0c43"
30+
// An inquiry that isn't for sales skips Salesforce and EmailOctopus. It's emailed here, titled with its reason, to be
31+
// forwarded to the team that handles it.
32+
const CONTACT_INBOX_EMAIL = "stefan@anoma.ly"
2333

2434
function splitFullName(fullName: string) {
2535
const parts = fullName
@@ -42,10 +52,19 @@ function escapeHtml(value: unknown) {
4252

4353
export function enterpriseEmailContent(body: EnterpriseFormData, inferenceSpend?: string) {
4454
const field = (value?: unknown) => (value ? `${escapeHtml(value)}<br>` : "")
55+
const requirements = [
56+
body.inRegionRouting && "In-region routing (EU or US)",
57+
body.dpa && "Data Processing Agreement (DPA)",
58+
].filter((item) => typeof item === "string")
59+
const details = [
60+
inferenceSpend && `Current monthly inference spend: ${inferenceSpend}`,
61+
body.country && `Country: ${body.country}`,
62+
requirements.length > 0 && `Compliance requirements: ${requirements.join(", ")}`,
63+
].filter((item) => typeof item === "string")
4564
return {
4665
html: `
4766
${escapeHtml(body.message)}<br><br>
48-
${inferenceSpend ? `Current monthly inference spend: ${escapeHtml(inferenceSpend)}<br><br>` : ""}
67+
${details.length > 0 ? `${details.map(escapeHtml).join("<br>")}<br><br>` : ""}
4968
--<br>
5069
${field(body.name)}
5170
${field(body.role)}
@@ -54,7 +73,7 @@ ${field(body.phone)}`.trim(),
5473
text: [
5574
body.message,
5675
"",
57-
...(inferenceSpend ? [`Current monthly inference spend: ${inferenceSpend}`, ""] : []),
76+
...(details.length > 0 ? [...details, ""] : []),
5877
"--",
5978
...[body.name, body.role, body.company, body.email, body.phone].filter(Boolean),
6079
].join("\n"),
@@ -97,6 +116,7 @@ export async function handleEnterpriseRequest(
97116
options: {
98117
salesforceSubmit?: (payload: Record<string, unknown>) => Promise<boolean>
99118
notifications?: boolean
119+
send?: typeof sendEmail
100120
spam?: Parameters<typeof enterpriseSpamProbability>[1]
101121
captcha?: Parameters<typeof verifyHcaptcha>[1]
102122
} = {},
@@ -110,7 +130,10 @@ export async function handleEnterpriseRequest(
110130
return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
111131
}
112132

113-
if (!body.name || !body.role || !body.email || !body.message) {
133+
// A sales inquiry names its sender and their role; any other inquiry only needs its reason.
134+
const reason = body.reason === undefined ? undefined : contactReasons.find((item) => item.value === body.reason)
135+
const complete = body.reason === undefined ? Boolean(body.name && body.role) : reason !== undefined
136+
if (!body.email || !body.message || !complete) {
114137
return Response.json({ error: dict["enterprise.form.error.allFieldsRequired"] }, { status: 400 })
115138
}
116139

@@ -119,6 +142,16 @@ export async function handleEnterpriseRequest(
119142
return Response.json({ error: dict["enterprise.form.error.invalidEmailFormat"] }, { status: 400 })
120143
}
121144

145+
// The Lead's Country holds up to 80 characters.
146+
const structured =
147+
(body.country === undefined || (typeof body.country === "string" && body.country.trim().length <= 80)) &&
148+
(body.inRegionRouting === undefined || typeof body.inRegionRouting === "boolean") &&
149+
(body.dpa === undefined || typeof body.dpa === "boolean")
150+
if (!structured) {
151+
return Response.json({ error: dict["enterprise.form.error.allFieldsRequired"] }, { status: 400 })
152+
}
153+
const country = body.country?.trim() || undefined
154+
122155
const inferenceSpend = inferenceSpendOptions.find((option) => option.value === body.inferenceSpend)
123156
if (body.inferenceSpend && !inferenceSpend) {
124157
return Response.json({ error: dict["enterprise.form.error.invalidInferenceSpend"] }, { status: 400 })
@@ -145,7 +178,27 @@ export async function handleEnterpriseRequest(
145178
return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
146179
}
147180

148-
const emailContent = enterpriseEmailContent(body, inferenceSpend?.salesforceValue)
181+
const send = options.send ?? sendEmail
182+
183+
if (reason) {
184+
const sent = await send({
185+
to: CONTACT_INBOX_EMAIL,
186+
subject: reason.label,
187+
body: `${escapeHtml(body.message)}<br><br>--<br>${escapeHtml(body.email)}`,
188+
text: [body.message, "", "--", body.email].join("\n"),
189+
replyTo: body.email,
190+
}).then(
191+
() => true,
192+
(err) => {
193+
console.error("Failed to send contact email:", err)
194+
return false
195+
},
196+
)
197+
if (!sent) return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 })
198+
return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
199+
}
200+
201+
const emailContent = enterpriseEmailContent({ ...body, country }, inferenceSpend?.salesforceValue)
149202

150203
const [lead, mail, octopus] = await Promise.all([
151204
createLead(
@@ -155,7 +208,10 @@ export async function handleEnterpriseRequest(
155208
company: body.company,
156209
email: body.email,
157210
phone: body.phone,
211+
country,
158212
inferenceSpend: inferenceSpend?.salesforceValue,
213+
inRegionRouting: body.inRegionRouting === true,
214+
dpa: body.dpa === true,
159215
message: body.message,
160216
},
161217
options.salesforceSubmit,
@@ -165,7 +221,7 @@ export async function handleEnterpriseRequest(
165221
}),
166222
options.notifications === false
167223
? Promise.resolve(false)
168-
: sendEmail({
224+
: send({
169225
to: Resource.ENTERPRISE_SALES_INBOX_EMAIL.value,
170226
subject: `Enterprise Inquiry from ${body.name}`,
171227
body: emailContent.html,

‎packages/console/app/src/lib/salesforce.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ test("a fake entry keeps the visitor message, spend range, and enterprise lead s
5959
Title: "Engineering lead",
6060
Description: " We need SSO for our developers.\nPlease share the enterprise options. ",
6161
Current_Monthly_Inference_Spend__c: "$10K–$50K",
62+
In_Region_Routing__c: false,
63+
DPA_Required__c: false,
6264
LeadSource: "enterprise website form",
6365
})
6466
})
@@ -74,7 +76,10 @@ test("the local Enterprise request handler applies the updated mapping to a form
7476
role: "Engineering lead",
7577
company: "Example Test Company",
7678
email: "fake-local-visitor@example.test",
79+
country: " Germany ",
7780
inferenceSpend: "50k-100k",
81+
inRegionRouting: true,
82+
dpa: false,
7883
message: " How are you bro!\nThis is a local form test. ",
7984
alias: "",
8085
captcha: "test-token",
@@ -99,7 +104,10 @@ test("the local Enterprise request handler applies the updated mapping to a form
99104
expect(leads.at(-1)).toMatchObject({
100105
LastName: "Fake Local Form Visitor",
101106
Description: " How are you bro!\nThis is a local form test. ",
107+
Country: "Germany",
102108
Current_Monthly_Inference_Spend__c: "$50K–$100K",
109+
In_Region_Routing__c: true,
110+
DPA_Required__c: false,
103111
LeadSource: "enterprise website form",
104112
})
105113
})

‎packages/console/app/src/lib/salesforce.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ export interface SalesforceLeadInput {
4444
company?: string
4545
email: string
4646
phone?: string
47+
country?: string
4748
inferenceSpend?: string
49+
inRegionRouting?: boolean
50+
dpa?: boolean
4851
message: string
4952
}
5053

@@ -58,8 +61,11 @@ export async function createLead(
5861
Email: input.email,
5962
Phone: input.phone ?? null,
6063
Title: input.role,
64+
Country: input.country,
6165
Description: input.message,
6266
Current_Monthly_Inference_Spend__c: input.inferenceSpend,
67+
In_Region_Routing__c: input.inRegionRouting ?? false,
68+
DPA_Required__c: input.dpa ?? false,
6369
LeadSource: "enterprise website form",
6470
}
6571
if (submit) return submit(payload)

0 commit comments

Comments
 (0)