Skip to main content

What already works, the moment you sign up

account.signup returns an account, a project, both environments, and four keys — a secret and a publishable one per environment, shown once and never again. You can send email immediately, with nothing verified. Every project gets a sender on its own subdomain of Lath’s sending domain, and transactional and sign-in mail go out from it. Verifying a domain of your own is what puts your name on it and what marketing needs; it is not what sending needs. Sign-in by email code and by email link work out of the box. Passwords are the one method off by default. The owner seat is invited rather than active until somebody follows the emailed link. The keys work either way, which is why a script can sign up and send before anyone has opened the mail.

Before marketing email

Seven things are checked, and you do not have to discover them one at a time: email.broadcast.create returns them all as readiness problems, each with its own fix, long before anything sends. email.broadcast.send then refuses on the first one that still stands. A sender you are allowed to send from, a topic that exists, a segment that exists if you named one, a template that exists and is published, not one of the reserved sign-in templates, a working unsubscribe link, and a postal address in the email footer. Each problem carries its own code in that list, so your own code can branch on which one it is rather than matching prose. They are readiness codes rather than refusal codes — a distinction worth keeping straight, because two of them, sender_unverified and topic_unknown, are also what you are refused with if you ignore readiness and send anyway. The last one catches almost everybody, because it is law rather than plumbing and it is empty by default. It lives in the theme’s copy, and it must be published, not merely saved as a draft. Two more can refuse at send time and are about the account rather than the message: a closed account, and an invoice past due — and past due pauses marketing only, after a grace period. Transactional mail keeps going.

Before text messages

Two separate things: a carrier registration that has reached active for the project, and a phone number this environment owns. Registration is filed once per project and moves draft → registering → active on its own; sms.registration.get returns a plain-language blocker for whatever state it is in. sms.send does not refuse while you wait. It stores the message and answers with a blocked object beside it, saying why and what would unblock it. The message is real, findable and not sent — which is a different thing from an error, and the reason a send that “worked” may not have gone anywhere. Read the reply. Quiet hours are on by default, 21:00 to 08:00, with no timezone set — and without a timezone there is no clock to measure them against, so marketing text is refused until you set one or turn quiet hours off. The readiness problem names the missing timezone, and sms.settings.get reports it on its own. Messages caught by quiet hours are delayed, never dropped. A marketing text must tell people how to stop, and readiness says so when it does not. It is not added for you, because adding it would change the segment count you just measured.

Before the hosted sign-in pages

Allowed redirect origins start empty, so nothing is allowed. Until you list yours, sending somebody back to your app is refused with redirect_not_allowed, and a magic link has nowhere to land. This is the first thing to set and the easiest to forget, because the hosted pages look finished without it. Passkeys bind to an origin and are never bound to Lath’s hostname. The relying-party id derives from your first allowed origin; with none, passkeys refuse with passkey_unconfigured. Social sign-in works through Lath’s own apps, which means the consent screen says Lath. Registering your own puts your name there instead — auth.oauth.app.list hands you the exact callback address to register with the provider. Publish the theme. account.theme.set saves a draft; the hosted pages and your mail both read the published one. Publishing and resetting are live-only, and a palette below WCAG AA is refused with contrast_too_low — which names a nearby colour that would pass rather than leaving you to guess. Lath will not let you switch off the last way in: a settings change that would leave no method both enabled and available is refused with no_usable_signin_method.

What test mode will not do

Anything that changes what live users receive needs the live key, refused otherwise with live_only: publishing or resetting a theme, setting the sender, removing a domain, and setting your own OAuth app. Test email only reaches active members of your own account — a plus-tagged address folds to the member’s mailbox, and anyone else is refused with test_mode_recipient. An invited member who has not accepted yet counts as anyone else. Test mode sends no text messages at all, to anybody, for the same reason: there is no safe pretend recipient for a carrier. Fifty messages per environment per rolling day, refused with test_mode_limit. Test sends are never billed. Broadcasts do not refuse on any of this — they count the recipients they held, so the number you see is the number that would have gone.

Ask the product, not this page

This page will go out of date; the fields below cannot, because they are computed from the same code that does the refusing. Prefer them to any checklist, including this one. email.broadcast.create and email.automation.activate return readiness problems with fixes. sms.settings.get returns whether texting is ready and what is in the way. sms.registration.get and email.domain.get each return a single blocker, null when there is nothing wrong. auth.settings.get returns every sign-in method with whether it is enabled, whether it is available, and why not. In the dashboard, the same answers are on the page that owns them, and anything account-wide that needs you is on the environment’s home page.
Last modified on September 16, 2026