Skip to content

Authenticate the public API

Mail's public sending endpoint is authenticated with a product public key: where it is created, what its scope covers, how to replace one, and why a key removed on Mail's side is not revoked.

Mail’s public sending endpoint is authenticated with a product public key — a credential the platform issues, holds and checks. It stands for your product: the key is matched against the product’s active keys before the request reaches Mail at all, so a key belonging to one product opens nothing in another.

The key is not a Mail concept. It is minted under the product’s settings, it covers routes rather than sending domains, and the same key can cover public routes of other toolkits in the same product.

The lpk_ prefix

A public key is lpk_ followed by 32 hexadecimal characters — 128 bits of randomness. The prefix marks the string as a product public key, so a key that turns up in a log or a configuration file is recognisable for what it is.

The platform stores the key’s SHA-256 and the first eight characters of the hex body, and nothing else of the secret. Those eight characters are what you see when you list your keys, and they are how you tell one key from another after the fact.

Not every key in the list begins lpk_. A key the platform imported from Mail keeps the lmk_ prefix and the value it already had — see keys imported from Mail.

Create a key

A product Owner or Admin creates a key in the workspace, under the product’s Settings → Public Access. Creating one asks for a name and a scope, and the plaintext key is shown once, on the screen that creates it.

That screen is the only place the secret ever appears. The platform keeps a one-way hash, so nothing can hand it back to you later — not the workspace, not support. Put it into your secret store as you create it. A key you did not save is not recoverable; revoke it and create another.

What a key covers

A key’s scope is one of three things.

ScopeWhat it covers
Every public route of the productEvery public route of every toolkit in the product, including ones added later.
Every public route of one toolkitFor Mail, both keyed routes below and nothing outside Mail.
Exactly one routeOne toolkit, one method, one path — for example POST /emails.

A scope names route coordinates. It knows nothing about sending domains: a key that covers POST /emails may send from any verified domain of the product. Scope a key to the single route an integration needs rather than to everything, and give each system its own key. Then one leak is one key to revoke, and it reaches one route.

Key changes take about thirty seconds

Keys are served from a snapshot refreshed on an interval — about thirty seconds by default. Creating a key, changing its scope or revoking one takes effect within one such window, not immediately.

So a key you have just created answers 401 for a few seconds before it starts working, and a key you have just revoked keeps working for a few seconds after. Wait out the window rather than testing a fresh key in a tight loop, and say so in your own onboarding copy if you hand keys to someone else.

Replace a key

There is no rotation endpoint and no rotation button. Replacing a key is three steps, in this order.

  1. Create a new key with the same scope.
  2. Deploy the new secret everywhere the old one is used, and confirm nothing is still calling with the old one.
  3. Revoke the old key, under the product’s Settings → Public Access.

Both keys are valid in between, which is what makes the changeover safe. Doing it in that order means there is no moment when your application has no working key. Revocation is permanent and cannot be undone — a revoked key is replaced, never restored.

Use a key on a request

The key goes in the Authorization header as a bearer token:

Authorization: Bearer lpk_…

Two other headers carry it, and they are read in this order: Authorization: Bearer first, then X-Public-Key, then X-Api-Key. The last is a legacy alias, kept so that a client written against an older release keeps working unchanged — that is the header an older Mail integration already sends its lmk_… key in, and an imported key presented there is accepted. All three headers carry whichever key you hold, minted or imported. Write new code against Authorization.

Any other Authorization scheme is ignored. The key is never a query parameter. All three headers are removed before the request reaches Mail, so the key is not visible to the toolkit and never lands in Mail’s logs.

Mail’s public sending endpoint is https://public.lessly.com/{product_id}/mail, with two routes on it, both of which require a key.

RouteWhat it does
POST /emailsSends an email.
GET /emails/{id}Returns an email and its latest event.

So a send is:

curl https://public.lessly.com/$PRODUCT_ID/mail/emails \
  -H "Authorization: Bearer $LESSLY_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@mail.example.com",
    "to": "customer@example.com",
    "subject": "Welcome",
    "html": "<p>Hello</p>"
  }'

Mail’s other public routes — the open and click tracking links and the one-click unsubscribe — take no key and reject none. They are opened by the recipient’s mail client, from a link inside a message you sent, where no key could be presented. Each is gated by a signed token in its own URL instead.

When a request is refused for its key

Four answers come from the platform edge rather than from Mail.

ResponseMeaning
401 public_key_requiredNo key was presented on a route that needs one.
401 public_key_invalidThe key matches no active key of this product. It was mistyped, it belongs to another product, or it has been revoked and the snapshot has caught up.
403 public_key_scopeThe key is valid, but its scope does not cover this route. A key revoked within the last window arrives here too.
503 public_config_unavailableNo snapshot has been loaded yet. Nothing is wrong with your key; retry.

Everything else Mail answers is unrelated to keys and means what it has always meant: a validation error on the message, 404 for an email that does not exist, 413 for a body over the cap, 429 for the send rate limit.

Store a key

A public key that covers POST /emails is a sending credential. Anyone holding it can send email as any verified domain of your product, and mail sent that way is signed by your domain and looks entirely legitimate to the people who receive it. For system email that is the sharp end: a forged password-reset link arriving from the address your customers have been taught to trust.

  • Keep keys on a server you control. Read them from your secret store or from the environment at run time.
  • Never ship a key in a browser application, a mobile app, or a desktop app. Anything shipped to a device can be extracted from it. Have the device call your own backend and let the backend hold the key.
  • Never commit a key to a repository, and never paste one into an issue, a chat message or a support ticket.
  • Give each system its own key, named for that system, and scope it to the one route that system calls.
  • Revoke a key the moment you suspect it is exposed. Revoke it under Settings → Public Access — that is the only place a revocation takes — and remember it keeps working for up to one propagation window afterwards.

Keys imported from Mail

If you hold a Mail API key, it still works and there is nothing for you to do. Mail used to issue its own sending keys — the ones beginning lmk_ — and every active one was copied into the product’s key list. Those keys are public keys now, and they authenticate the endpoint above exactly as a minted lpk_ key does.

  • The value is the same. An imported key keeps its lmk_ prefix and its original value. Nothing was re-issued, so no integration had to be reconfigured, and no secret had to be redeployed.
  • The header is the same. A caller already sending its key as Authorization: Bearer, X-Public-Key or X-Api-Key keeps working as it is — including an older SDK release sending X-Api-Key, which stays accepted as a legacy alias. A caller using some other custom header has to move to one of the three, and new code should use Authorization: Bearer.
  • They are listed with the rest. An imported key appears among the product’s public keys under Settings → Public Access, marked there as imported and keeping the name and creation date it had.
  • They cover Mail and only Mail — both keyed routes above, POST /emails and GET /emails/{id}, and no route of any other toolkit in the product.
  • They are ordinary keys from then on. Under Settings → Public Access an Owner or Admin can widen or narrow their scope and revoke them exactly as with a minted key, and revocation reaches the edge in the same propagation window. The plaintext is not shown again, the same way it is not shown for a minted key after creation.

They are no longer tied to a sending domain, and that widens what their holder may do. A Mail key could be locked to one verified domain. A public key’s scope names route coordinates and knows nothing about domains, so a key that could only send from one domain now sends from every verified domain of the product. Read that as a privilege change, not a detail: a key you gave a contractor, an agency or a third-party service for one domain now reaches all of them. Per-domain restriction no longer exists, so if the wider reach is not what you intend, revoke that key and hand out a new one scoped to the single route the holder needs.

The Mail sending key is a different key

Mail issues a sending key of its own, and it is not the credential this page is about. Keeping the two apart is the whole point of this section.

Product public keyMail sending key
What it is forAuthenticating Mail’s public sending endpoint — POST /emails and GET /emails/{id}Mail’s own API
Prefixlpk_lmk_
Issued byThe platform, under the product’s Settings → Public AccessMail, with mail_apikey_create — a scoped sending key whose full secret is returned only once
Revoked byThe platform, under Settings → Public Accessmail_apikey_delete, by id
Listed byThe product’s key list under Settings → Public Accessmail_apikey_list — active keys, prefix and metadata only, never the secret

A Mail sending key is scoped sending_access or full_access, and may optionally be locked to one of your domains. Like a public key it is stored as a hash plus a visible prefix and shown in full only at creation, so a key you did not save is replaced rather than recovered. A request refused for that key on Mail’s own API answers 401 restricted_api_key.

Both keys exist and both work. The one that authenticates the public sending endpoint is the product public key described above — and an lmk_ key you already held is accepted there too, because it was imported into the product’s key list, as the section above explains. The import is what removes the domain lock on the public endpoint: a public key’s scope names routes, not domains.

Because an imported key now lives in two records at once — Mail’s own, and the product’s key list — closing off public access means revoking it under Settings → Public Access. That is the record the public endpoint checks. Revoking Mail’s own key with mail_apikey_delete is what closes off Mail’s own API.

Next steps

Was this page helpful?
Esc

Start typing to search the docs.

navigateselect