One message, one payment, one receipt — Telegram and SMTP email delivered per-message with the proof of delivery in the response.
git clone https://github.com/nirholas/x402-notify
cd x402-notify && npm install
npm run dev # http://localhost:4025
| Route | Price | What you get back |
|---|---|---|
POST /telegram | $0.002 | `{messageId, chat, deliveredAt}` — the message id and the chat Telegram resolved, usable later to reply to or edit the message |
POST /email | $0.002 | the SMTP acceptance receipt — raw `250 …` response, reply code, Message-ID, host — plus accepted/rejected recipients and the envelope as sent |
GET /health | free | Liveness, data source, configured rails |
GET /.well-known/x402 | free | Machine-readable discovery manifest |
Every paid route returns the purchased artifact in the 200 body — nothing is deferred to a webhook or a second fetch.
POST /telegramX-PAYMENTPay in USDC on Base or Solana — your client picks. The 402 body always lists both.
USDC on base-sepolia (or base). The wallet signs an EIP-3009 authorization locally — no server round-trip.
USDC SPL transfer on solana. A facilitator sponsor pays the SOL network fee, so buyers need only USDC. Helpers at /api/x402-checkout build and wrap the transaction.
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment } from "x402-fetch";
const payFetch = wrapFetchWithPayment(fetch, privateKeyToAccount(process.env.PRIVATE_KEY));
// Ping a chat — $0.002
const tg = await (await payFetch("http://localhost:4025/telegram", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chatId: "123456789", text: "Deploy finished." }),
})).json();
// Or email, with the SMTP acceptance receipt — $0.002
const mail = await (await payFetch("http://localhost:4025/email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ to: "dana@example.com", subject: "Deploy finished", text: "All green." }),
})).json();
console.log(mail.receipt.smtpResponse); // "250 2.0.0 OK 1738012345 ..."
Telegram delivers for real when `TELEGRAM_BOT_TOKEN` is set (a free token from @BotFather). Email relays for real when `SMTP_HOST` is set (any server: Gmail app password, Postmark, SES, Mailtrap, a local relay). Each channel is independent — you can run one live and one dry. Unconfigured channels return `"source": "fixture"`, `"delivered": false` and a `dryRunReason` explaining exactly what was skipped. **Nothing ever reports a delivery that didn't happen.**