Emails & Account Recovery
Which emails ZeepNotti sends and when, password reset and email verification, auth rate limits, and the environment variables that control email and client IP resolution on self-hosted installs.
ZeepNotti sends HTML emails (with a plain-text alternative) for account events, security events and operational problems with your Apps. On SaaS they are delivered by Zeep; on a self-hosted install they go through the Company's SendGrid key (Company Settings). If a self-hosted Company has no SendGrid key, emails are skipped and logged - nothing else breaks.
Every email except invites is sent in the background by the worker, with retries. A failed email never fails the action that triggered it.
Emails sent
Alerts go to every Owner of the Company. Alerts with a dedup window are sent at most once per window - a burst of identical failures produces one email, not one per delivery.
| Sent when | Recipient | Dedup | |
|---|---|---|---|
invite-new-user | Someone without an account is invited | Invitee | - |
company-added | An existing account is added to a Company | Invitee | - |
welcome | Onboarding creates a Company | New Owner | - |
verify-email | Onboarding, or Resend email from the dashboard banner | The user | 60s throttle |
password-reset | Forgot password? is submitted for an existing account | The user | 60s throttle |
member-removed | An Owner removes a member | Removed member | - |
access-changed | An Owner changes a member's role or App access | That member | Only when something actually changed |
key-rotated | A REST or Client key is rotated | Owners | None - every rotation |
provider-not-configured | A delivery fails because FCM/APNs is not configured | Owners | Once per App + platform per 24h |
app-disabled-pending | Pending deliveries are failed because the App was disabled | Owners | Once per disable event |
mau-exceeded | A notification is rejected by the MAU limit (SaaS) | Owners | Once per App per 30 days |
limit-reached | An invite or App creation is rejected by the plan's user/App limit (SaaS) | Owners | Once per Company + resource per 24h |
delivery-digest | Daily: an App had ≥100 deliveries and ≥10% failed the previous UTC day | Owners | Once per App per day |
invalid-tokens | Daily: provider-invalidated devices for an App/platform were ≥500, or ≥5% of its subscribed base, the previous UTC day | Owners | Once per App + platform per day |
mau-warning | Daily: a SaaS App's MAU crossed 80% or 90% of its plan limit | Owners | Once per threshold per App per 30 days |
The daily checks run hourly on every worker and always look at the previous UTC day; the dedup record makes sure each email is sent exactly once even with several worker replicas.
Key-rotation emails include only the key prefix (rk_/ck_ plus the first
4 characters), never the key itself. There are no per-user alert
preferences yet - every Owner receives every alert.
Password reset
- On the login screen, click Forgot password? and enter your email. ZeepNotti always answers the same way, whether or not the account exists, so the form can't be used to discover who has an account.
- If the account exists, a
password-resetemail arrives with a link to/reset-password. The link is single-use and expires after 1 hour. Requesting again within 60 seconds does not send another email. - Choose a new password. Once it's saved:
- every other outstanding reset link for that account stops working;
- every session and refresh token issued before the reset is rejected, so other signed-in browsers are signed out on their next request;
- the email address is marked as verified.
An invalid, expired or already-used link shows an error with a Request a new link action.
Rate limits
The public account endpoints are rate limited per client IP, in fixed
1-minute windows shared by every server replica (counters live in Redis).
Going over the budget answers 429 rate_limited with a Retry-After
header in seconds.
| Endpoint | Requests per minute |
|---|---|
POST /v1/auth/login | 10 |
POST /v1/auth/forgot-password | 5 |
POST /v1/auth/reset-password | 10 |
POST /v1/auth/verify-email | 10 |
POST /v1/auth/accept-invite | 10 |
POST /v1/auth/refresh | 30 |
POST /v1/companies (onboarding) | 5 |
If Redis is unreachable the limits are skipped (requests go through) and the error is logged, so an outage never locks admins out.
Email verification
New Owners get a verify-email message during onboarding. The link opens
/verify-email, is single-use and expires after 24 hours. Until the
address is verified, the dashboard shows a dismissible banner with a
Resend email action.
Accepting an invite or resetting a password also marks the address as verified, since both prove you can read that mailbox. Verification is informational today: an unverified account can still sign in and use the dashboard.
Self-hosted configuration
| Variable | Required | Description |
|---|---|---|
NOTTI_DASHBOARD_BASE_URL | Yes, for email | Public dashboard origin (e.g. https://notti.example.com). Every link in an email - invite, password reset, verification, alerts - is built from it. |
NOTTI_INVITE_TOKEN_SECRET | Yes, to invite members | Signs member invite links. |
NOTTI_EMAIL_FROM_ADDRESS | Yes, for SendGrid | Sender address for emails sent through the Company's SendGrid key. |
NOTTI_EMAIL_LOGO_URL | No | Absolute https URL of the logo shown in emails. Defaults to {NOTTI_DASHBOARD_BASE_URL}/email/notti-logo-white.png, served by ZeepNotti itself. Set it when the dashboard isn't reachable from your recipients' mail clients (e.g. a private network), otherwise the logo shows as alt text. |
NOTTI_DOCS_URL | No | Docs link in the welcome email. Defaults to https://docs.zeepnotti.app. |
NOTTI_TRUSTED_PROXIES | Behind a proxy | Comma-separated CIDRs (e.g. 10.0.0.0/8,fd00::/8) of the reverse proxies or load balancers in front of ZeepNotti. When a request comes from one of them, the client IP is the right-most X-Forwarded-For entry that isn't itself a trusted proxy. Unset, the TCP peer address is used and X-Forwarded-For is ignored - behind a proxy that means every client shares the proxy's rate-limit budget. An invalid CIDR fails startup. |
ZEEP_NOTIFICATIONS_BASE_URL and ZEEP_NOTIFICATIONS_API_KEY configure
email delivery for the SaaS deployment only; self-hosted installs don't
need them.