Email

Send email to your users through CZL Connect's mail infrastructure, without running your own SMTP server, sending domain or unsubscribe handling. Three ways to send:

MethodRecipientsContentHow
Verification codesAny addressFixed template; CZL Connect generates and verifies the codeAPI (email:code)
Custom emailYour app's usersHTML or Markdown, unsubscribe link added automaticallyAPI (email:send)
BroadcastsYour app's users + manually added addressesMarkdown, can be scheduledApp details → Email, or API (broadcast:write)

See App API for keys and authentication: create a key under App details → API access with the scopes you need, send Authorization: Bearer czlc_live_..., base URL https://connect.czl.net/api/v1/app.

Availability and quota

Available by the app owner's plan. All three methods share the account's monthly email quota (all apps in the account combined, calendar month in Beijing time):

PlanMonthly email quotaDaily verification codes
LiteNot availableNot available
Plus5,0002,000
Pro30,00010,000
EnterpriseUnlimited50,000

A verification code counts only once it is sent. Custom emails and broadcasts count per recipient entering the send queue; failed deliveries are not refunded.

Sender address

The display name is "App name (via CZL Connect)" and the default address is [email protected]. After setting a sender alias under App details → Email, the address becomes czlconnect_<alias>@notify.czl.net, so recipients can tell apps apart by address.

  • Aliases are 3-20 lowercase letters, digits or hyphens, unique across the site
  • Setting an alias requires a plan that includes email (Plus or above) and passkey verification
  • The alias can only be set once and can't be changed or cleared by you afterwards; contact support if you really need to change it. A replaced alias stays reserved for its original app for 90 days, so other apps can't take it
  • Recipients' filters and allowlists for the old address stop working after a change
  • Aliases cannot contain words that impersonate official functions or well-known brands (such as admin, billing, security, apple, paypal)
  • The sender address does not receive mail; don't ask users to reply

Verification codes

For sending codes to people who aren't your users yet: confirming a guest is a real person before subscribing, verifying an address before sign-up, or double-checking before account deletion. CZL Connect generates, sends and verifies the code; your backend only gets a code id.

Send

POST /api/v1/app/email-codes
Authorization: Bearer czlc_live_...
Content-Type: application/json

{
  "email": "[email protected]",
  "purpose": "subscribe",
  "locale": "en",
  "clientIp": "203.0.113.9"
}
FieldRequiredDescription
emailYesRecipient address, any address
purposeNoDetermines the subject and wording, defaults to general, see below
localeNoEmail language zh / en, defaults to zh
clientIpRecommendedThe end user's IP, used for per-IP rate limiting; only public addresses are accepted, omit to skip per-IP limits
purposeSubject
generalApp name verification code
signupApp name sign-up verification code
loginApp name sign-in verification code
subscribeApp name subscription confirmation code
delete_accountApp name account deletion verification code (body warns the action cannot be undone)

The email is sent synchronously and the call returns once the mail server accepts it (usually a second or two), with no queueing:

{ "id": "ec_Xh3...", "expiresIn": 600, "expiresAt": "2026-11-14T10:20:00+08:00" }

Codes are 6 digits and valid for 10 minutes.

Verify

POST /api/v1/app/email-codes/verify
Content-Type: application/json

{ "id": "ec_Xh3...", "code": "482913" }
{ "verified": true, "email": "[email protected]", "purpose": "subscribe" }

A code is voided as soon as it verifies. A wrong code returns code_mismatch with attemptsRemaining; after 5 wrong attempts the code is voided and a new one must be sent. Record the email from this response, not the one submitted by the client.

Rate limits

ScopeLimit
Same app, same recipient1 per minute
Same recipient (all apps)10 per 24 hours
Same app, same clientIp20 per hour
Account (all apps)Daily verification code limit, see the plan table

We recommend a 60-second countdown on your "resend" button. A few mailbox providers greylist first-time senders, delaying delivery by a few minutes; ask users to check their spam folder on the verification screen.

Custom email

Send notifications to your app's users (order status, account reminders and so on) with your own content.

POST /api/v1/app/emails
Content-Type: application/json

{
  "userIds": ["1024", "2048"],
  "subject": "Your order has shipped",
  "html": "<p>Order <b>#1234</b> has shipped.</p>"
}
FieldDescription
userIdsUser IDs in this app (strings or numbers), 1-100 per call
subjectSubject, up to 200 characters
html / markdownBody, exactly one, up to 200 KB

HTML is sanitized: inline styles, table layouts and http(s) images and links are kept; scripts, forms, iframe and event attributes are removed. CZL Connect wraps the body (app name, why the user received it, unsubscribe link) and adds a one-click List-Unsubscribe header.

Returns 202 with a result per recipient. Accepted emails enter the send queue immediately and usually go out within seconds:

{
  "queued": 1,
  "results": [
    { "userId": "1024", "status": "queued" },
    { "userId": "2048", "status": "skipped", "reason": "unsubscribed" }
  ]
}
reasonDescription
not_app_userNot a user of this app
bannedBanned site-wide or disabled in this app
unsubscribedUnsubscribed from this app's notifications
email_unavailableNo verified real email address (such as QQ / WeChat sign-in without an added email)
rate_limitedThis user has already received 20 custom emails today

Once a user unsubscribes, neither broadcasts nor custom emails are sent to them; verification codes are not affected. Custom emails not yet sent when the service restarts may be lost; for must-deliver content, rely on your own business state.

Broadcasts

Same rules as creating a broadcast under App details → Email: a recipient snapshot is taken at creation, unsubscribed and banned users are excluded again at send time, broadcasts can be scheduled, and tasks that haven't started can be canceled.

POST /api/v1/app/broadcasts
Content-Type: application/json

{
  "subject": "November update",
  "markdown": "## What's new\n\n- ...",
  "audience": "all",
  "groups": ["t1", "t2"],
  "scheduledAt": "2026-11-20T10:00:00+08:00"
}
FieldDescription
audienceall all users / single one user (with userId) / exclude exclude some users (with excludeUserIds) / extra_only manually added addresses only
groupsFor all / exclude, filter by in-app group; empty means all
extraEmailsExtra external addresses (users who haven't signed in with CZL Connect), up to 1000 per task, counted against the quota
scheduledAtRFC 3339 send time; empty sends immediately (picked up by a job that runs every minute)

Other endpoints:

EndpointDescription
GET /broadcasts?page=&pageSize=List tasks, newest first
GET /broadcasts/:broadcastIdTask status and counts (sentCount / failedCount / skippedCount)
POST /broadcasts/:broadcastId/cancelCancel a task that hasn't started sending

Quota

GET /api/v1/app/mail/quota

Requires any of email:code, email:send or broadcast:write. Returns the sender address, daily verification code limit, this app's usage today and this month, and the account's monthly quota.

Error codes

Errors look like {"error": "<code>", "message": "<description>"}:

HTTPerrorDescription
400invalid_request / invalid_email / invalid_purposeInvalid parameters
400code_mismatchWrong code, with attemptsRemaining
400code_expiredCode doesn't exist, has expired or was already used
400code_attempts_exceededToo many wrong attempts, the code was voided
403mail_disabledNot available on the current plan
403quota_exceededMonthly email quota used up
429rate_limitedRecipient or IP rate limit hit
429daily_limit_exceededThe account's daily verification code limit was reached
502send_failedThe email could not be sent; retry later
503temporarily_unavailableService busy; retry later