Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/lifecycle/src/campaign/templates.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ describe('renderCampaignTemplate', () => {
})
).toEqual([]);
for (const link of [
'https://calendar.app.google/nK961tWHZd21izKR6',
'https://calendar.app.google/someone-else',
'https://calendly.com/threadplane/demo',
'https://cal.com/threadplane/demo',
Expand Down
11 changes: 6 additions & 5 deletions apps/lifecycle/src/campaign/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ export type CampaignEvidenceAngle =
const CampaignStepSchema = z.enum(['immediate', 'day-3', 'day-8']);

/**
* Brian's Google Calendar appointment-schedule booking page. This is the only
* scheduling link recipient copy may carry; every other calendar host or path
* is still rejected by the draft checks below.
* First-party redirect to Brian's Google Calendar appointment-schedule booking
* page (configured in the website's `next.config.ts`). Recipient copy links
* here instead of the calendar host so every URL matches the sending domain.
* This is the only scheduling link recipient copy may carry; every other
* calendar host or path is still rejected by the draft checks below.
*/
export const FOUNDER_BOOKING_URL =
'https://calendar.app.google/nK961tWHZd21izKR6';
export const FOUNDER_BOOKING_URL = 'https://threadplane.ai/call';

/**
* Every link recipient copy may carry, across campaign steps and fulfillment
Expand Down
18 changes: 18 additions & 0 deletions apps/website/next.config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,21 @@ describe('website next.config redirects for the removed chat configuration API',
}
});
});

/**
* Lifecycle mail links to `/call` rather than the Google Calendar host so every
* URL in the message matches the sending domain. The redirect must stay
* temporary so the booking page can move without stale browser caches.
*/
describe('website next.config founder booking redirect', () => {
it('temporarily redirects /call to the Google Calendar booking page', async () => {
const redirects = await config.redirects!();
const rule = redirects.find((r: { source: string }) => r.source === '/call');

expect(rule).toBeTruthy();
expect(rule.destination).toBe(
'https://calendar.app.google/nK961tWHZd21izKR6'
);
expect(rule.permanent).toBe(false);
});
});
8 changes: 8 additions & 0 deletions apps/website/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ export const nextConfig: WithNxOptions = {
destination: '/api/markdown/chat/getting-started/installation',
permanent: true,
},
// Founder booking link for lifecycle mail. Recipient copy carries this
// first-party URL so every link matches the sending domain; the temporary
// redirect lets the calendar page change without touching sent mail.
{
source: '/call',
destination: 'https://calendar.app.google/nK961tWHZd21izKR6',
permanent: false,
},
],
rewrites: async () => [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ Copy constraints:
- At most one question and at most one useful link.
- No banner, button, HTML layout, tracking pixel, open tracking, or click rewriting.
- Never say “I saw you…” based on telemetry.
- The only scheduling link allowed is Brian’s Google Calendar appointment-schedule booking page (`FOUNDER_BOOKING_URL` in the campaign templates); every other calendar host or path is rejected.
- The only scheduling link allowed is Brian’s Google Calendar appointment-schedule booking page, linked through the first-party `https://threadplane.ai/call` redirect so URLs match the sending domain (`FOUNDER_BOOKING_URL` in the campaign templates); every other calendar host or path is rejected.
- From and Reply-To are Brian at Threadplane <brian@threadplane.ai>.
- BCC Brian on every recipient-facing email that may begin a conversation. The email carries X-Threadplane-Job-ID so the Google poller can register the BCC seed’s actual RFC Message-ID; seed copies are never treated as recipient replies.

Expand Down
Loading