Billing & Credits
Relevant source files
The following files were used as context for generating this wiki page:
The Billing & Credits system in Send Again is a credit-based infrastructure used to manage email sending quotas and commercial transactions. It integrates with Polar for payment processing and subscription management, allowing workspaces to purchase credit packs or operate within a free tier.
The system ensures that email sends are authorized based on the workspace's available balance, unless specifically bypassed by administrative rules. It tracks lifetime purchases, consumption, and real-time balance across multiple domains.
Sources: README.md, src/app/api/billing/status/route.ts
System Architecture
The billing architecture consists of a local database state synchronized with Polar via webhooks and API calls. The core logic resides in src/lib/billing.ts, while src/lib/polar.ts handles external communications.
Core Components
| Component | Description |
|---|---|
Local DB (workspace_billing) | Stores the authoritative credit balance, consumption stats, and cached Polar identifiers. |
| Credit Packs | Defined JSON structures representing purchasable "top-up" units. |
| Polar Integration | Handles Checkout sessions, Customer Portals, and Webhooks for payment confirmation. |
| Enforcement Logic | Middleware and API logic that checks creditBalance before initiating send jobs. |
Sources: src/lib/db.ts:1015-1033, src/lib/billing.ts:13-35
Data Flow: Credit Purchase
When a user purchases credits, the system coordinates between the local application and Polar's checkout infrastructure.
The diagram above shows the lifecycle of a credit purchase from user intent to database update.
Sources: src/app/api/billing/checkout/route.ts:130-165, src/app/api/webhooks/polar/route.ts:135-175
Credit Management
Credits are the primary currency for sending emails. 1 recipient send consumes 1 credit.
Free Tier and Initial Grants
When a new workspace is created, it is granted an initial set of free credits defined by the FREE_TIER_INITIAL_CREDITS environment variable (default: 1000). These are tracked in lifetime_credits_purchased but are effectively a grant.
Sources: src/lib/billing.ts:13-18, src/lib/db.ts:1100-1115
Credit Consumption
Consumption occurs when a send job is processed. The system uses a database RPC consume_workspace_credits to ensure atomic updates to the balance and consumption counters.
-- Conceptual logic of consume_workspace_credits
UPDATE workspace_billing
SET
credit_balance = credit_balance - p_credits,
lifetime_credits_consumed = lifetime_credits_consumed + p_credits
WHERE workspace_id = p_workspace_id;
Sources: src/lib/db.ts:1210-1230
Billing Bypass
Certain users or conditions allow for unlimited sending without credit checks. This is controlled by environment variables:
BILLING_UNLIMITED_AUTH_EMAILS: A list of emails that bypass enforcement.BILLING_UNLIMITED_USER_IDS: A list of user IDs that bypass enforcement.
Sources: src/lib/billing.ts:40-60
Polar Integration
Polar acts as the merchant of record. The application interacts with Polar using an organization token and a webhook secret.
Webhook Handling
The system listens for several Polar events to keep the local state in sync:
| Event Type | Logic |
|---|---|
subscription.* | Updates billing_status, polar_subscription_id, and current period dates. |
order.paid | Resolves credits from the order/product metadata and applies a top-up. |
customer.state_changed | Synchronizes the overall customer state and active subscriptions. |
Sources: src/app/api/webhooks/polar/route.ts:85-200
Credit Resolution from Orders
When an order.paid event is received, the system attempts to determine the number of credits to award in the following priority order:
creditskey in Order metadata.email_credits(configurable key) in Order metadata.- Matching the
pack_idin metadata to a localCreditPack. - Matching the
productIdto a localCreditPack. email_creditskey in Product metadata.
Sources: src/app/api/webhooks/polar/route.ts:50-80
Data Models
Workspace Billing Table (workspace_billing)
This table extends the workspace with commercial data.
| Field | Type | Description |
|---|---|---|
workspace_id | text (PK) | The domain/id of the workspace. |
credit_balance | int | Current spendable credits. |
billing_status | text | active, past_due, canceled, or inactive. |
lifetime_credits_purchased | int | Total credits ever granted/bought. |
lifetime_credits_consumed | int | Total credits ever spent. |
polar_customer_id | text | Polar's internal ID for the customer. |
polar_subscription_id | text | Current active subscription ID. |
Sources: src/lib/db.ts:1015-1033, src/lib/billing.ts:65-75
API Endpoints
The billing system provides several REST endpoints for management and status reporting.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/billing/status | Returns the current credit balance and billing enforcement status. |
GET | /api/billing/packs | Lists available credit packs for purchase. |
POST | /api/billing/checkout | Creates a Polar checkout session for a specific credit pack. |
POST | /api/billing/portal | Generates a link to the Polar customer portal for managing billing. |
Sources: src/app/api/billing/status/route.ts, src/app/api/billing/checkout/route.ts
Status Synchronization
The GET /api/billing/status endpoint triggers an background synchronization with Polar to ensure the local billing_status and subscription dates are accurate before returning data to the UI.
Sources: src/app/api/billing/status/route.ts:55-75
Conclusion
The billing system provides a robust, pay-as-you-go credit mechanism integrated with modern payment infrastructure. By decoupling credit consumption (local DB) from payment processing (Polar), the application maintains high performance for email sending while ensuring commercial compliance.
Sources: README.md, src/lib/billing.ts