Extending the Billing Provider
Relevant source files
The following files were used as context for generating this wiki page:
The Send Again platform utilizes a credit-based billing system where one email send consumes one credit. The current implementation is tightly integrated with Polar, but the architecture is designed to handle usage enforcement and credit management via internal database procedures, allowing for the addition of other providers.
Sources: src/app/api/send/route.ts:244-245, README.md:135-145
Core Billing Architecture
The billing system revolves around the workspace_billing table and two primary PostgreSQL RPC functions that handle atomic credit operations. This decoupling ensures that the application logic for "sending" is separated from the "purchasing" logic of a specific provider.
Data Model and Persistence
The database maintains a WorkspaceBilling record for each workspace, tracking balances and synchronization state with external providers.
| Field | Type | Description |
|---|---|---|
credit_balance | Integer | Current available credits for the workspace. |
lifetime_credits_purchased | Integer | Total credits ever added to the account. |
lifetime_credits_consumed | Integer | Total credits used for successful email sends. |
polar_external_customer_id | String | A mapping ID used to link the workspace to the provider. |
billing_status | Enum | Current state (e.g., active, inactive, past_due). |
Sources: src/lib/db.ts:815-832, src/lib/db.ts:868-885
Atomic Credit Operations
Credit management is performed through RPC calls to ensure data integrity during concurrent send jobs.
consume_workspace_credits: Deducts credits from the balance. If the balance is insufficient, the operation fails, preventing the send.apply_workspace_credit_topup: Increases the balance when a purchase is confirmed via webhook.
Provider Integration Flow
Adding or extending a provider requires implementing two main interfaces: the Checkout Flow and the Webhook Handler.
Checkout Flow
The checkout flow initiates a session with the payment provider. In the current implementation, POST /api/billing/checkout validates the workspace ownership and selects a "pack" from the configuration before creating a provider-specific session.
Webhook Handling
Providers must notify Send Again when a payment is successful. The webhook handler is responsible for resolving the workspace_id from the provider's payload and calling the top-up RPC.
| Event Type | Action Taken |
|---|---|
order.paid | Resolves credits from metadata and calls applyWorkspaceCreditTopup. |
subscription.created/updated | Syncs subscription status and period dates to the database. |
customer.state_changed | Updates the local billing_status to match the provider. |
Sources: src/app/api/webhooks/polar/route.ts:100-150, src/app/api/webhooks/polar/route.ts:160-185
Usage Enforcement and Bypassing
Billing enforcement is controlled globally via environment variables, but can be bypassed at the user or workspace level for administrative or development purposes.
Enforcement Logic
When BILLING_ENFORCED is set to true, the /api/send endpoint calculates the required credits (1 per recipient) and attempts to consume them before starting the job.
// src/app/api/send/route.ts
if (!body.dryRun && isBillingEnforced() && !hasEnoughCredits) {
return NextResponse.json(
{ error: "Insufficient credits. Please top up your balance..." },
{ status: 402 }
);
}
Sources: src/app/api/send/route.ts:255-264
Bypass Mechanisms
The system provides several ways to bypass credit checks:
- Environment Lists:
BILLING_UNLIMITED_AUTH_EMAILSandBILLING_UNLIMITED_USER_IDSallow specific users to send without consuming credits. - Initial Grants: New workspaces receive a set amount of free credits defined by
FREE_TIER_INITIAL_CREDITS.
Sources: README.md:147-150, src/lib/db.ts:912-915
Configuration Summary
To extend or configure billing, the following environment variables are utilized:
| Variable | Description | Default |
|---|---|---|
BILLING_ENFORCED | Whether to block sends if credits are 0. | false |
FREE_TIER_INITIAL_CREDITS | Credits granted upon workspace creation. | 1000 |
POLAR_CREDIT_PACKS_JSON | Defines available purchase options (id, credits, price). | — |
POLAR_WEBHOOK_SECRET | Secret used to verify incoming payment notifications. | — |
Sources: README.md:135-155
Conclusion
Extending the billing provider in Send Again involves mapping a new provider's checkout and webhook events to the internal workspace_billing table and the associated RPC functions. The system's reliance on atomic database operations for consumption ensures that credit enforcement remains robust regardless of the external payment gateway used.