driptab/Docs

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:

  1. Reads final counters: e.g. mail_in=1250, mail_out=640, storage_bytes=10_485_760
  2. Computes fees for each charge using the snapshotted rate
  3. Writes the invoice to D1 atomically with its fee rows
  4. Fires invoice.created webhook

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:

  1. Terminate the current subscription (invoices the current period's usage).
  2. 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.