# Send Email and Verification Codes with Integrations | Creght

> Connect Resend in the Creght editor, then use ctx.email.send / sendCode / verifyCode in Func to send mail and verify email codes without putting credentials in your code.

[![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)API for AI](/)

[View llms.txt](/llms.txt)

Overview

- [Creght API for AI](/api.md)

Discoverability

- [How to optimize llms.txt](/api/optimize-llms-txt.md)

Site configuration

- [Implement domain-based locale routing](/api/domain-locale-routing.md)

Backend

- [Build site backend workflows with Func](/api/func-backend.md)
- [Integrate Alipay PC Web Payment with Func](/api/func-alipay-payment.md)

Integrations

- [Send Email and Verification Codes with Integrations](/api/func-email-integration.md)

On this page

- [Integration or environment variable](#when)
- [Connect Resend](#connect)
- [Send email](#send)
- [Email verification codes](#code)
- [What the integration lets you configure](#config)
- [When you need something the platform has not wrapped](#escape)
- [Errors and boundaries](#errors)

Integrations/Send Email and Verification Codes with Integrations

# Send Email and Verification Codes with Integrations

Connect the Resend integration and use ctx.email in Func for transactional email and email verification codes: the credential stays server-side, and code length, expiry, single use, constant-time comparison, attempt caps, and rate limiting are handled by the platform.

Copy Markdown link

Integrations let you connect a third-party service once in the editor, then call it from Func as a platform capability. The credential stays on the server and never appears in Func code, logs, or return values. Resend is supported today, for transactional email and email verification codes.

**Scope**

This page covers the email capability: `ctx.email.send`, `ctx.email.sendCode`, and `ctx.email.verifyCode`. Payments have no managed integration and no `ctx.payment`; write that logic in Func yourself, see [Integrate Alipay PC Web Payment with Func](/api/func-alipay-payment.md).

## Integration or environment variable

Both exist side by side. The rule is simple:

| Situation | Use |
| --- | --- |
| A supported service, standard usage (sending mail, verification codes) | Connect the integration, call `ctx.email.*` |
| An unsupported service, or a provider-specific advanced API | Put the key in an environment variable and call it with `fetch` |

An integration is not there to make the API call for you. It removes the "am I actually connected?" uncertainty: the key is validated for real when you save it, the sending address is picked from the domains your provider account has already verified, and you can send one real test email on the spot.

## Connect Resend

1. Create an API key in the [Resend dashboard](https://resend.com/api-keys) and verify your sending domain there.
2. Open **Backend → Integrations** in the editor and paste the API key into the Resend card. Saving calls Resend for real to validate the key, so an invalid key fails immediately instead of at send time in production.
3. Pick the sending address from the **verified domains** that were read back, for example `noreply@yourdomain.com`. Sender name and reply-to are optional.
4. Click **Send test email** to send a real message and confirm the whole path works.

> The sending address must belong to a domain that is **verified** in Resend. Resend rejects mail from unverified domains, which is why this is a pick-from-list rather than a free-text field.

## Send email

Once connected, call it directly in Func. No credential appears in your code:

```typescript
import type { TalizenFuncContext } from 'talizen/func-runtime'

export function notifyOwner(input, ctx: TalizenFuncContext) {
  const { id } = ctx.email.send({
    to: 'owner@example.com',
    subject: 'New booking: ' + input.name,
    html: '<p>' + input.name + ' booked ' + input.date + '</p>',
  })
  return { id }
}
```

`to` accepts one address or an array, up to 50 recipients per send. `subject` is required and at least one of `html` / `text` must be present. Returns `{ id, provider }`. Omitting `from` / `replyTo` falls back to what the integration is configured with.

## Email verification codes

Do not hand-roll verification codes. `sendCode` and `verifyCode` already handle the parts that are easy to get wrong:

```typescript
import type { TalizenFuncContext } from 'talizen/func-runtime'

export function sendLoginCode(input, ctx: TalizenFuncContext) {
  ctx.email.sendCode({ to: input.email, scene: 'login' })
  return { ok: true }
}

export function verifyLoginCode(input, ctx: TalizenFuncContext) {
  const ok = ctx.email.verifyCode({
    to: input.email,
    scene: 'login',
    code: input.code,
  })
  if (!ok) throw new Error('invalid or expired code')
  return { ok: true }
}
```

`scene` namespaces codes by purpose, so a login code and a password-reset code for the same address never collide. It defaults to `default` and must be 1–32 characters of letters, digits, underscore, or dash.

| Guaranteed by the platform | Default |
| --- | --- |
| Random numeric code length | 6 digits |
| Expiry | 10 minutes (configurable up to 60) |
| Single use: consumed on a successful check | Yes |
| Constant-time comparison, no prefix leak | Yes |
| Wrong-attempt cap per code, then invalidated | 5 attempts |
| Send rate per recipient per scene | 5 per 10 minutes |
| Daily verification emails per project | 500, configurable per project (max 10000) |

> **Do not reimplement any of it:** no `Math.random()` codes, no storing codes in `ctx.db` or `ctx.cache` with your own expiry, no hand-rolled attempt counters. A wrong guess does not extend the code's lifetime — an easy detail to miss when writing this yourself.

`verifyCode` returns `false` for a wrong code, an expired code, and no code at all rather than throwing; it only errors on invalid arguments or a service failure. So `if (!ok)` is all your branch needs.

> A verified code proves control of the mailbox. It is **not** a session. Establish login state through project auth, see the [Func backend guide](/api/func-backend.md).

## What the integration lets you configure

| Setting | Effect |
| --- | --- |
| Sending address | Default `from`; must come from a verified domain |
| Sender name | Display name the recipient sees |
| Reply-to address | Default `replyTo` |
| Verification email subject | Supports the `{{code}}` placeholder |
| Code expiry | 10 minutes by default, 60 maximum |
| Code length | 6 digits by default |
| Daily cap | Verification emails per day for this project; 500 by default, 10000 maximum |

## When you need something the platform has not wrapped

By default an integration does **not** hand the credential to the Func sandbox. To use other provider APIs (Resend audiences, batch sending, attachments), or to switch providers and send it yourself, turn on **expose\_to\_func** in the integration: the key is additionally projected into `process.env` ( `RESEND_API_KEY` for Resend) and you can call anything with `fetch`.

> The toggle is off by default. Once on, Func code can read the credential — turn it on only when you actually need it.

## Errors and boundaries

- `ctx.email.*` errors when nothing is connected, validation has not passed, or the integration is disabled. That is a configuration action in the editor — do not work around it by calling the provider API directly.
- It errors when the integration has no sending address; set one under **Backend → Integrations**.
- An empty `subject`, empty `html` and `text`, or more than 50 recipients all return 400.
- Hitting the send rate limit returns 429 with a try-again-later message.
- All three methods are synchronous, like `ctx.db` and `ctx.cache`; writing `await` is harmless.
- Capabilities live on `ctx` only. Func has no module loader, so a value import such as `import { email } from 'talizen/func-runtime'` always fails. Use `import type` for types.

**Func fundamentals**

Files, invocation, JSON tables, auth, asset uploads, timeouts, and SSE are covered in the [Func backend guide](/api/func-backend.md).

![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)

This website is built with [Creght](/)

[Discord](https://discord.gg/Qvq2nmZNnb)

## Links

- [Pricing](/price.md)
- [Solutions](/solution.md)
- [Customers](/customers.md)
- [Help Center](/help.md)
- [Contact Us](/contact.md)
- [Update Logs & Blogs](/blogs.md)
- [Refund Policy](/tuikuan.md)

## Resources

- [All Resources](/resources.md)
- [Templates](/templates.md)
- [Components](https://creghtlib.site.creght.com)
- [Animations](/effects.md)
- [Figma to Creght](/figma2creght.md)
- [API for AI](/api.md)

## Terms

- [Terms of Service](/legal/terms.md)
- [Privacy Policy](/legal/privacy.md)
- [Acceptable Use Policy](/legal/acceptable-use.md)

## Social Media

- [Twitter](https://twitter.com)
- [Facebook](https://www.facebook.com)
- [LinkedIn](https://www.linkedin.com)

[蜀ICP备2023038192号-2](https://beian.miit.gov.cn)
