Sceptical about the automated warmup services. They work by sending fake mail between accounts in a pool and marking it as important, and providers have had years to learn what a reciprocal network looks like. A mailbox with 400 warm conversations and zero real threads is itself a signature.
At 30 a day you can do the honest version: send 10 genuinely personalised emails a day for two weeks to people likely to reply, and reply to everything that comes back. That is warmup. It is just slower and it involves talking to humans, which is the point of the exercise anyway.
Whatever you decide, turn on tax ID collection at checkout now. Both the main billing tools let you ask for a VAT number on the payment page and validate it, which means the invoice comes out correct automatically and you stop getting these emails a month after the sale.
Retrofitting the number onto an invoice that has already been paid is the annoying version of this problem. Collecting it before the charge is a checkbox.
Only thing I'd add is checking it on a phone over a throttled connection. My hero image was 1.8MB and on a mid-range Android over patchy 4G the page was blank for several seconds. Everyone who left in that window counted as a visitor with no scroll.
Compressed it and served a smaller version to phones, and my 'nobody scrolls' problem turned out to be a 'nobody sees anything' problem.
One person owns a ticket end to end, including the follow up. Handoffs mid-conversation are where customers get told two different things and lose confidence in both of you.
I shipped sample data and then removed it, so here is the failure mode in detail.
Sample data made the first screen look great and the activation number went up, briefly, because I was measuring the wrong thing. People clicked around, felt satisfied, and left. My real activation event is a completed import within seven days, and that number went down about six points while the shallow one went up.
What I did not anticipate: support tickets from people asking how to delete the sample project, and two people who thought the sample numbers were their own data pulled from somewhere, which was a bad thirty minutes of email.
If you do it anyway, put a persistent dismissible banner on every sample object saying what it is and offering one-click removal.
We measured review time per hundred lines before we changed anything, which I would recommend before you pick a fix. Ours came out at roughly 11 minutes per hundred lines for hand written code and 17 for generated code, because reviewers were checking for plausible looking nonsense as well as correctness. That number is the argument you take to your lead: it is not that there is more code, it is that each line costs more to review than it used to.
Also question the cadence. Ask who consumes it and how fresh they actually need it. Half the hourly models I inherit are hourly because somebody typed hourly, and moving to every four hours cuts the bill by 75 percent with nobody noticing.
The month-three conviction is so consistent that it should have a name. Enough people get an early winning streak by chance that a large fraction of new traders spend their second year learning it was chance.
I have run both shapes. Eight small clients paid the same as three large ones and cost me roughly double the unbilled hours - kickoff calls, invoices, chasing, onboarding to their tools, one Slack workspace each. The large-client version is more fragile and less exhausting; the small-client version is more resilient and slowly eats your evenings. Neither is correct, but you should choose one deliberately rather than accumulate the second by accident.
Two short heavy sessions, on your hard days, dropped to one in the last ten days before the race. Do not start a new lift inside the final three weeks.
There is also a practical hazard people skip past, which is inhalation. Aerosols get breathed in by a small child who is being sprayed at head height and by whoever is holding him. Do it outdoors, downwind, and never in a car or a beach tent. There have also been recalls of some aerosol batches over contamination in the past few years, which is its own reason to prefer a pump if the brand offers one.
The clock measuring their calendar is a good way to put it. We ended up counting sessions to first value instead of minutes, which removed most of the noise.
Make the write idempotent rather than trying to make it not fail. Retries are a fact of life; a pipeline that only works when nothing crashes is not a pipeline, it is a streak.
The standard pattern for what you describe:
Write to a staging location keyed by run, not by table. Something like staging/dt=2026-07-30/run_id=abc123/. A retry writes to a new run_id and the partial output of the failed attempt is simply orphaned.
Make the publish into the warehouse one atomic operation over the whole partition: delete-then-insert inside a transaction, a partition swap, or a MERGE on a real primary key. Not row by row appends.
Clean up orphaned staging prefixes on a schedule or with a lifecycle rule.
The key shift is that the unit of work is 'this partition is now correct', not 'these rows have been added'. Once any task can be rerun any number of times and land in the same state, on-call stops deleting rows by hand.
At 2 million rows over 40 partitions, delete-insert per partition costs almost nothing and is by far the easiest of the three to reason about.
The bit that is easy to skip: check that you are allowed to hand over the customer data at all.
Your privacy policy should say something about a change of ownership. If it does not, you have a problem you want to find now rather than after. Any enterprise-ish customer contract may have a change of control clause requiring notice or consent. And decide when customers hear about it, which in my opinion is the day it happens, from you, in plain language, with a name and a face attached to the new owner.
Also do not send the list before the money is genuinely in escrow. A spreadsheet of customers is the only asset in this deal that cannot be un-sent.
Add a contract test on the upstream schema. Ten lines that fail loudly when a column type changes turn a mysterious 3am mess into a clear message before anything downstream runs.
Fail on a type change or a missing column, warn on a new column. New columns are usually harmless and blocking on them trains people to ignore the alert.
Twenty five minutes for a dimension that is correct by construction is a very good trade when there are two of you. Merge logic drifts silently, and the failure mode is not a red pipeline, it is a slightly wrong customer count nobody catches for a quarter. Rebuilds have no drift because there is no state to drift.
The threshold to switch is when the rebuild starts affecting an SLA, or when it gets expensive enough that somebody asks about it. At 40 million rows in 25 minutes you are nowhere near either.
Also, you have told us the keys are imperfect across three systems. The merge is asking you to be certain about the one thing you have said you are unsure about.