Guide: SaaS metering (EmailFS)
EmailFS sells email infrastructure plans: domain routing, inbound/outbound mail volume, and R2 storage. This guide shows how to use Driptab for usage-based billing across multiple metrics and plan tiers.
This is the canonical end-to-end scenario for Driptab. For the full reference, see
docs/usecase-emailfs.md.
The billing model
EmailFS reports three metrics:
| Metric | Code | Aggregation | Unit |
|---|---|---|---|
| Inbound mail | mail_in |
sum | per message |
| Outbound mail | mail_out |
sum | per message |
| R2 storage | storage_bytes |
sum | per byte written |
Plan tiers:
| Plan | Base fee | mail_in rate | mail_out rate | storage rate |
|---|---|---|---|---|
| Starter | $0 | $0.001 | $0.002 | $0.00001 |
| Pro | $29 | $0.0005 | $0.001 | $0.000005 |
| Team | $99 | $0.0002 | $0.0005 | $0.000002 |
All charges use invoice_timing: arrears — customers pay at the end of each month
based on actual usage.
Step 1 — Authenticate and set context
dtab token set dtab_f36951fb6dd443babd9df35d32ae1e0f
dtab use acme/default/billing
dtab whoami
Step 2 — Create billable metrics
dtab metrics create --code mail_in --name "Inbound Emails" --aggregation-type sum --field count
dtab metrics create --code mail_out --name "Outbound Emails" --aggregation-type sum --field count
dtab metrics create --code storage_bytes --name "R2 Storage" --aggregation-type sum --field bytes
List to get IDs:
dtab metrics list
CODE NAME AGG FIELD ID
mail_in Inbound Emails sum count <mail_in_id>
mail_out Outbound Emails sum count <mail_out_id>
storage_bytes R2 Storage sum bytes <storage_bytes_id>
Step 3 — Create plans
Starter ($0 base + per-use)
dtab plans create \
--code emailfs-starter \
--name "Starter" \
--interval monthly \
--amount 0 \
--currency USD
dtab plans add-charge <starter_id> --model standard --metric <mail_in_id> --rate 0.001 --timing arrears
dtab plans add-charge <starter_id> --model standard --metric <mail_out_id> --rate 0.002 --timing arrears
dtab plans add-charge <starter_id> --model standard --metric <storage_bytes_id> --rate 0.00001 --timing arrears
Pro ($29 base + lower rates)
dtab plans create \
--code emailfs-pro \
--name "Pro" \
--interval monthly \
--amount 2900 \
--currency USD
dtab plans add-charge <pro_id> --model standard --metric <mail_in_id> --rate 0.0005 --timing arrears
dtab plans add-charge <pro_id> --model standard --metric <mail_out_id> --rate 0.001 --timing arrears
dtab plans add-charge <pro_id> --model standard --metric <storage_bytes_id> --rate 0.000005 --timing arrears
Team ($99 base + lowest rates)
dtab plans create \
--code emailfs-team \
--name "Team" \
--interval monthly \
--amount 9900 \
--currency USD
dtab plans add-charge <team_id> --model standard --metric <mail_in_id> --rate 0.0002 --timing arrears
dtab plans add-charge <team_id> --model standard --metric <mail_out_id> --rate 0.0005 --timing arrears
dtab plans add-charge <team_id> --model standard --metric <storage_bytes_id> --rate 0.000002 --timing arrears
Verify:
dtab plans list
Step 4 — Create a customer
EmailFS registers each account as a Driptab customer at sign-up. The --external-id
is EmailFS's internal account.id.
dtab cust create \
--external-id emailfs_account_abc123 \
--name "Ali Yilmaz" \
--email ali@acme.com \
--currency USD
Step 5 — Start a subscription
When Ali selects the Starter plan:
dtab subs create \
--external-id emailfs_sub_abc123 \
--customer ext:emailfs_account_abc123 \
--plan emailfs-starter
This starts the SubscriptionMeter DO and schedules the end-of-month alarm.
Step 6 — Ingest usage events
EmailFS fires a Driptab event for every billable action. The --transaction-id must
be unique per event — use deterministic IDs based on your internal record IDs.
Inbound message received:
dtab ev ingest \
--transaction-id msg_01HX4K2MNPQRSTUVWXYZ000001 \
--subscription ext:emailfs_sub_abc123 \
--code mail_in \
--value 1
Outbound message sent:
dtab ev ingest \
--transaction-id send_01HX4K2MNPQRSTUVWXYZ000002 \
--subscription ext:emailfs_sub_abc123 \
--code mail_out \
--value 1
R2 write:
dtab ev ingest \
--transaction-id r2_01HX4K2MNPQRSTUVWXYZ000003 \
--subscription ext:emailfs_sub_abc123 \
--code storage_bytes \
--value 24576
Duplicate handling:
If you send the same --transaction-id again (e.g. after a retry):
dtab ev ingest \
--transaction-id msg_01HX4K2MNPQRSTUVWXYZ000001 \
--subscription ext:emailfs_sub_abc123 \
--code mail_in \
--value 1
# Response: { "accepted": true, "duplicate": true }
The counter is not changed.
Step 7 — Integration points in EmailFS code
In EmailFS's backend, wire up event ingestion at the natural trigger points:
// quota.ts — called after successful mail receive
async function incrementUsage(subscriptionExternalId: string, code: string, value: number) {
await dtab.events.ingest({
transaction_id: `${code}_${messageId}`,
subscription_id: `ext:${subscriptionExternalId}`,
code,
value: String(value),
timestamp: new Date().toISOString(),
});
}
// On inbound mail received:
await incrementUsage(account.driptabSubId, 'mail_in', 1);
// On outbound mail sent:
await incrementUsage(account.driptabSubId, 'mail_out', 1);
// storage.ts — called after R2 write
await incrementUsage(account.driptabSubId, 'storage_bytes', bytesWritten);
Use deterministic transaction_id patterns so retries are safe:
transaction_id: `mail_in_${message.id}`
transaction_id: `mail_out_${outbox.id}`
transaction_id: `r2_${hash(r2Key)}`
Step 8 — Period close and invoicing
At month end, the DO alarm fires automatically. Driptab:
- Reads final counters: e.g. mail_in=1250, mail_out=640, storage_bytes=10_485_760
- Computes fees for each charge using the snapshotted rate
- Writes the invoice to D1 atomically with its fee rows
- Fires
invoice.createdwebhook
EmailFS's webhook handler collects payment and records it:
if (event === 'invoice.created') {
const { invoice, customer } = data;
const charge = await stripe.paymentIntents.create({
amount: invoice.total_cents,
currency: invoice.currency.toLowerCase(),
customer: customer.external_id,
});
await dtab.payments.record({
invoice_id: invoice.id,
amount_cents: invoice.total_cents,
currency: invoice.currency,
provider: 'stripe',
provider_payment_id: charge.id,
paid_at: new Date().toISOString(),
});
}
Step 9 — View invoices
dtab inv list
NUMBER STATUS TOTAL ISSUED
INV-2026-0001 outstanding $3.47 2026-07-01
dtab inv get INV-2026-0001
The invoice shows a fee row for each charge: mail_in, mail_out, and storage_bytes, with the units aggregated and the rate that was applied.
Plan upgrade
When Ali upgrades from Starter to Pro:
- Terminate the current subscription (invoices the current period's usage).
- Create a new subscription on the Pro plan.
dtab subs terminate ext:emailfs_sub_abc123
dtab subs create \
--external-id emailfs_sub_abc123_pro \
--customer ext:emailfs_account_abc123 \
--plan emailfs-pro
A plan-change workflow (without termination) will be added in a future release.