<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: mathew winter</title>
    <description>The latest articles on DEV Community by mathew winter (@mathew_winter_8d02b92d564).</description>
    <link>https://dev.to/mathew_winter_8d02b92d564</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4091007%2F2252707b-63a1-475e-b7c6-4ff74db3e4f4.png</url>
      <title>DEV Community: mathew winter</title>
      <link>https://dev.to/mathew_winter_8d02b92d564</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mathew_winter_8d02b92d564"/>
    <language>en</language>
    <item>
      <title>I shipped an invoicing app with no backend, no accounts and no user table</title>
      <dc:creator>mathew winter</dc:creator>
      <pubDate>Sun, 23 Aug 2026 16:22:03 +0000</pubDate>
      <link>https://dev.to/mathew_winter_8d02b92d564/i-shipped-an-invoicing-app-with-no-backend-no-accounts-and-no-user-table-47c8</link>
      <guid>https://dev.to/mathew_winter_8d02b92d564/i-shipped-an-invoicing-app-with-no-backend-no-accounts-and-no-user-table-47c8</guid>
      <description>&lt;p&gt;I got as far as sketching the &lt;code&gt;users&lt;/code&gt; table before I stopped.&lt;/p&gt;

&lt;p&gt;The app was going to be an invoice generator for sole traders — the kind of person who sends four invoices a month and has been quietly paying $15–25/month for the privilege. The default architecture was obvious: accounts, a server, sync across devices.&lt;/p&gt;

&lt;p&gt;Then I looked at what that table would actually hold. Business names. Tax registration numbers. Client lists. Billing histories. For thousands of small businesses, on my infrastructure, indefinitely, maintained by one person. I decided I didn't want to be responsible for it.&lt;/p&gt;

&lt;p&gt;So PDF Pebble ships with no accounts, no sign-up and no server-side storage. Invoices, documents and business details live on the device. This is a design post rather than a code post — it's about what that removed, what it made harder, and what it cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What disappears when there are no accounts
&lt;/h2&gt;

&lt;p&gt;More than I expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No auth flow. No password reset, no email verification, no OAuth, no session handling.&lt;/li&gt;
&lt;li&gt;No sync conflict resolution.&lt;/li&gt;
&lt;li&gt;No user data to breach, subpoena or migrate.&lt;/li&gt;
&lt;li&gt;No data-subject-access machinery, because there is no subject data.&lt;/li&gt;
&lt;li&gt;No onboarding funnel. The app opens and works. The single biggest drop-off point in this category doesn't exist, because there's nothing to drop off from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One precision, because "no backend" invites the wrong claim: the app is free and ad-supported, so it does talk to an ad network. Your documents don't go anywhere. The network isn't dead — the document path is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that got harder
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tax vocabulary is data, not conditionals
&lt;/h3&gt;

&lt;p&gt;An invoice is not a universal object. An Australian tax invoice carries an ABN and says GST. An Indian one needs a GSTIN. The UAE says TRN. Europe says VAT. The US says EIN.&lt;/p&gt;

&lt;p&gt;The tempting version is a chain of &lt;code&gt;if (region == "AU")&lt;/code&gt; branches running through the rendering code. That collapses by about the fourth region, because the branches aren't only about labels — they're about which fields are mandatory, what the document is legally called, and which registration identifier is the one that matters.&lt;/p&gt;

&lt;p&gt;Modelling a region as a data record — tax name, registration label, required fields — rather than as control flow was the change that made six regions cost roughly what two did.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. One document model, two unrelated render targets
&lt;/h3&gt;

&lt;p&gt;The same invoice has to come out as an A4 letterhead with a logo and a colour theme, and as a 58mm or 80mm thermal receipt.&lt;/p&gt;

&lt;p&gt;Those are not the same document at different scales. A4 has typography, whitespace and brand colour. A thermal receipt has a fixed character width, no colour and no useful concept of a margin. Anything that assumes a page — a header block, a two-column layout — needs a receipt-shaped answer or it has to go.&lt;/p&gt;

&lt;p&gt;Treating the invoice as structured data with two independent renderers, rather than as one layout that gets scaled down, is the only version that survives contact with a 58mm printer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Status with no server as the source of truth
&lt;/h3&gt;

&lt;p&gt;Invoices need tracking: draft to sent to paid, with a view of what's outstanding and overdue. Normally the server arbitrates that state.&lt;/p&gt;

&lt;p&gt;With no server, the device is the source of truth, and "sent" means "I handed this to the share sheet", not "our SMTP server accepted it". That's a weaker guarantee and worth being honest about in the model: the app tracks what you did, not what happened to the email afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;I'd rather list these than get caught out on them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No sync.&lt;/strong&gt; No second device, no phone-and-desktop continuity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No backup.&lt;/strong&gt; Lose the phone, lose the data. This is the real one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No retention signal.&lt;/strong&gt; No accounts means no cohort analysis, no activation funnel, no way to contact users. I traded the entire growth toolkit for a lower barrier and I'm still not certain that was right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For someone sending four invoices a month I think it's the correct trade. For a growing business it isn't, and I'd rather say that than pretend the architecture generalises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope, stated plainly
&lt;/h2&gt;

&lt;p&gt;It does: guided tax invoices with six regional presets, ABN lookup by client name in Australia, A4 and thermal output, draft/sent/paid tracking, batch document scanning into a single PDF with edge detection, PDF merge, QR and barcode generation.&lt;/p&gt;

&lt;p&gt;It doesn't: OCR — scanned text isn't searchable, and search matches file names only — password protection, e-signatures, editing inside an existing PDF, or cloud sync. Android and browser only; no iOS build yet.&lt;/p&gt;

&lt;p&gt;Free, ad-supported, no subscription.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pdfpebble.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;https://pdfpebble.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclosure: I built it. If you've shipped something offline-first, I'd genuinely like to know how you handled the backup problem — it's the objection I have the least good answer to.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>android</category>
      <category>architecture</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
