Skip to content

Send your first email

Verify a sending domain, create a public key and check a test email in your own mailbox.

Register and verify a sending domain, create a public key, then send a test email to your own mailbox. The seven steps below include one wait for DNS.

Set up the domain and key once. After that, your application sends each message with one request.

Steps 1–4 use Mail → Domains or the product-authenticated Mail API. Step 5 creates a platform credential under the product’s settings. Steps 6–7 use the public sending endpoint from your application; these are the steps that use the public key.

Before you start

You need
Why
A Lepsto product with Mail available
Every domain, template and message belongs to exactly one product
A domain your product already owns
Mail sends only from a domain the product governs
Access to that domain’s DNS
Step 3 publishes records at your DNS provider
A product Owner or Admin to hand
Only they can create the public key in step 5
A mailbox you can open
Step 6 sends the first message to you

This example product sends from mail.example.com. Replace the example domain, product ID, key, message ID and recipient address with your own values. Use a recipient mailbox you can open.

1. Find the domain you may send from

First, find an apex domain your product owns:

GET /mail/domains/allowed

Use mail_domain_allowed_list through MCP. The response lists the apex domains available to this product:

[
  { "domainId": "d3f1…", "domain": "example.com", "mode": "external", "extensionCount": 2 }
]

Register an apex from this list or one of its subdomains. A dedicated subdomain such as mail.example.com keeps application mail separate from your team’s own mail.

In the workspace, open Mail → Domains → Add domain. The Domain dropdown lists the same apexes and shows when none are available.

2. Add the domain

POST /mail/domains
{ "name": "mail.example.com" }

The new domain has status: "pending" and a records array. Save its id for the verification check in step 4.

In Add domain, select the apex. To use a subdomain, choose Add a subdomain and enter the prefix. Check the displayed sending domain, then select Add domain to open its detail page.

3. Publish the DNS records

At your DNS provider, create every record in the returned records array:

{
  "type": "CNAME",
  "name": "abc123._domainkey.mail.example.com",
  "value": "<the signing host Mail returns>",
  "purpose": "dkim",
  "required": true
}

Copy each record’s type, name and value; include priority for an MX record. Every record with required: true must resolve before verification succeeds. DMARC is optional for verification, but publishing the returned record is recommended.

Copy the names and values exactly. Some DNS providers append the zone name automatically, which can duplicate it. Keep any trailing dot in a returned value.

You can also copy the records from DNS records to publish on Mail → Domains → <domain>. The table identifies recommended records.

4. Wait for the domain to verify

Mail checks unverified domains every minute. To request a check immediately, call:

POST /mail/domains/{id}/verify

Use mail_domain_verify through MCP. Its response adds resolved to each record so you can identify records Mail can read.

Check the result: wait for status: "verified". DNS propagation usually takes minutes but can take hours; Mail continues checking for 72 hours.

Sending before verification returns 422 invalid_from_address.

The domain page refreshes every few seconds while verification is pending. Select Verify now to run a check immediately.

5. Create a public key

Create a product public key for the public sending endpoint. A product Owner or Admin opens Settings → Public Access in the workspace.

Name the key, choose Selected toolkits and select Mail. That workspace scope covers both public send and read routes. For an application that only sends, the management API can create a narrower key for POST /emails; see public access.

Step 7 reads the message through GET /emails/{id}. A send-only key returns 403 on that route. For this tutorial, either use the workspace key scoped to the whole Mail toolkit, or create a send-only key through the management API and keep that scope and check the message in the workspace. Step 7 describes both checks. Do not widen a key only to pass a check.

Save the key to your secret store when it appears. It is shown once and starts with lpk_ followed by 32 hexadecimal characters. A lost key cannot be recovered; revoke it and create another.

Allow about 30 seconds for the new key to take effect. Until the platform refreshes its key snapshot, it may return 401 public_key_invalid. That error can also mean a mistyped key or a key from another product. Check the value and product before waiting. See key refusal responses.

An existing imported lmk_ Mail key works as a product public key; you do not need a replacement for this step. See keys imported from Mail.

6. Send the first email

Send to https://public.lepsto.com/{product_id}/mail with your key in Authorization: Bearer. Use a from address on the domain you just verified:

curl https://public.lepsto.com/$PRODUCT_ID/mail/emails \
  -H "Authorization: Bearer $LEPSTO_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Example <hello@mail.example.com>",
    "to": "you@your-own-address.com",
    "subject": "Hello from Mail",
    "html": "<p>It works.</p>"
  }'

Or send the same message with the TypeScript SDK:

import { createMailer } from '@lepsto/sdk/mail';

const mail = createMailer({
  productId: process.env.LEPSTO_PRODUCT_ID!,
  publicKey: process.env.LEPSTO_PUBLIC_KEY!,
  baseUrl: 'https://public.lepsto.com',
});

const { id } = await mail.emails.send({
  from: 'Example <hello@mail.example.com>',
  to: 'you@your-own-address.com',
  subject: 'Hello from Mail',
  html: '<p>It works.</p>',
});

Both calls return the accepted email’s ID. HTTP returns it inside Mail’s response envelope:

{ "data": { "id": "1f4a…" }, "success": true }

The SDK unwraps data, so its send call resolves directly to { id }. HTTP examples on these pages include the envelope; SDK examples use the unwrapped result.

Check the result: save the returned ID for step 7. This request sends real mail to your test mailbox. If it returns 402, the organization’s plan check refused the send and no message was created; follow plan refusals.

An inline send needs from, at least one recipient, a subject, and html or text. For cc, bcc, reply-to, tags, scheduling and templates, see send a message.

7. Check what happened to it

An ID confirms acceptance. Check delivery through the workspace or the API, using the path your key’s scope permits.

Workspace check: open Mail → Logs, or enter the ID in Mail → Emails → Look up status. Open Mail → Logs → <email> for the ordered event history. These screens use your session and work even when the public key is send-only.

API check: use the following request only if the key covers GET /emails/{id}. A send-only key returns 403 public_key_scope; use the workspace check instead.

curl https://public.lepsto.com/$PRODUCT_ID/mail/emails/1f4a… \
  -H "Authorization: Bearer $LEPSTO_PUBLIC_KEY"
{
  "data": {
    "id": "1f4a…",
    "from": "Example <hello@mail.example.com>",
    "to": ["you@your-own-address.com"],
    "subject": "Hello from Mail",
    "status": "delivered",
    "created_at": "2026-08-02T09:00:00.000Z",
    "last_event": { "type": "email.delivered", "created_at": "2026-08-02T09:00:04.000Z" }
  },
  "success": true
}

With the SDK, read the unwrapped Email:

const email = await mail.emails.get(id);
console.log(email.status, email.last_event);

Check the result: confirm status: "delivered" and open the email in your test mailbox. queued means waiting to send, sent means handed to the receiving server, and delivered means that server accepted it. A hard rejection produces bounced; a spam report produces complained. See all message statuses.

For delivery notifications in your application, register a webhook.

Next steps

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select