Bills by email
Each organization gets an address like k7f3q9x2m4p8@in.example.com. Vendors send bills to it, or you forward them; PDF and image attachments land in the document inbox, including those of a forwarded email. A +tag is allowed (k7f3q9x2m4p8+fuel@in.example.com) and ignored.
Payvy doesn’t run a mail server. Mail reaches it through a front end that posts each raw message to POST /webhooks/email, signed with a shared secret. The supported front end is Cloudflare Email Routing with the small worker in the source repo (workers/email-inbound). It is free, and it is what hosted Payvy uses.
Self-hosting with Cloudflare
Section titled “Self-hosting with Cloudflare”You need a domain on Cloudflare (the free plan is enough). Use a subdomain for inbound mail, such as in.example.com, so it never touches mail for your main domain.
- Pick a secret of at least 32 characters, e.g.
openssl rand -hex 32. Keep it in your password manager; the server and the worker both need it. - Configure the server: set
PAYVY_INBOUND_DOMAIN=in.example.comandPAYVY_INBOUND_SECRET=<secret>, and restart./webhooks/emailmust be reachable from the internet (directly or through a Cloudflare Tunnel). - Deploy the worker from the source repo:
Terminal window cd workers/email-inboundnpm install# set PAYVY_URL in wrangler.toml to your server's public URLnpx wrangler secret put PAYVY_INBOUND_SECRETnpx wrangler deploy - Turn on Email Routing for the domain in the Cloudflare dashboard, add
in.example.comas a subdomain, and let Cloudflare create its MX and SPF records. - Route everything to the worker: in Email Routing’s routing rules, set the catch-all address for
in.example.comto Send to a Worker →payvy-email-inbound. - Create your address: a person runs
email.new_address(REST or MCP).email.addressshows it afterwards.
Send yourself a test bill and check email.list and document.list.
What the worker does
Section titled “What the worker does”- Retries a failed delivery twice, a few seconds apart.
- If Payvy is still unreachable, forwards the message to
FALLBACK_TOwhen you’ve set one (it must be a verified Email Routing destination), so nothing is lost. Otherwise it fails and Cloudflare reports the failure to the sending server. - Bounces mail to addresses Payvy didn’t issue, and messages over 25 MB.
Without Cloudflare
Section titled “Without Cloudflare”Anything that can run a script per message (Postfix with a pipe transport, a Mailgun or SES inbound hook, a small IMAP poller) can deliver instead. Post the raw message:
POST /webhooks/emailContent-Type: message/rfc822X-Payvy-Timestamp: <unix seconds, within 5 minutes of the server's clock>X-Payvy-Recipient: <the envelope recipient>X-Payvy-Signature: hex(HMAC-SHA256(secret, "{timestamp}.{recipient}.{hex(sha256(body))}"))
<the raw RFC 5322 message>