@meui-creative/meter
v0.1.0
Published
Transactional email counter for the MEUI hosting meter — wraps a Payload email adapter and pings the internal collector after each successful send (fire-and-forget, idempotent retries)
Readme
@meui-creative/meter
Transactional email counter for the MEUI hosting meter. Wraps a Payload CMS email adapter and pings the internal collector after each successful send — fire-and-forget, with retries and idempotency ids so a retry can never double-count and a failing ping can never break sending.
Pageview counting needs no package — sites include the snippet served by the admin app:
<script defer src="https://studio.meui.cz/m.js" data-key="YOUR_METER_KEY"></script>Installation
bun add @meui-creative/meterpayload (v3+) is a peer dependency — the package is meant to be used inside payload.config.ts.
Usage
import { withMeter } from '@meui-creative/meter'
import { resendAdapter } from '@payloadcms/email-resend'
export default buildConfig({
email: withMeter(
resendAdapter({
defaultFromAddress: '[email protected]',
defaultFromName: 'Example',
apiKey: process.env.RESEND_API_KEY || '',
}),
),
})Works with any Payload email adapter (Resend, Nodemailer/SES/SMTP, Mailjet, …) and also covers Payload-internal emails (password reset, verification).
For sends that bypass the adapter (direct provider SDK calls, batch APIs):
import { countEmails } from '@meui-creative/meter'
countEmails(recipients.length) // one logical send, N emailsEnvironment variables
One variable is enough:
METER_KEY=ab12cd34xy # site without transactional emails
METER_KEY=ab12cd34xy.NmQ3ZTk0... # site with emails: <key>.<secret>The key is base62 and the secret base64url — neither can contain a dot, so splitting on the first dot is unambiguous. Only the part before the dot may ever reach the browser (the snippet's data-key); use publicMeterKey() or process.env.METER_KEY?.split('.')[0] in your layout:
{process.env.METER_KEY && (
<script defer src="https://studio.meui.cz/m.js" data-key={process.env.METER_KEY.split('.')[0]} />
)}| Variable | Required | Purpose |
| -------------- | -------- | ------------------------------------------------------------------------------ |
| METER_KEY | yes | Site key, optionally combined <key>.<secret>. |
| METER_SECRET | no | Secret separately (wins over the combined form). Never ship it client-side. |
| METER_URL | no | Collector override. Defaults to https://studio.meui.cz/api/meter. |
Without a key + secret the wrapper is a transparent no-op — safe to keep in code on staging/preview environments.
Semantics
- Counts accepted-by-provider sends (industry ESP billing semantics), not delivery.
- A failed
sendEmail(throw) is not counted. - Pings retry 3× with backoff; each logical send carries one idempotency id (
i), the collector deduplicates. - The meter can never break sending — every failure path is swallowed.
Wire contract (v1) and collector design: meui-studio/docs/Project Management/hosting-meter.md.
