<?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: Daniel Wilson Kemp</title>
    <description>The latest articles on DEV Community by Daniel Wilson Kemp (@daniel_wilsonkemp_62b88a).</description>
    <link>https://dev.to/daniel_wilsonkemp_62b88a</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%2F4043109%2F01a2baab-c124-4624-b91f-00bd17e00346.png</url>
      <title>DEV Community: Daniel Wilson Kemp</title>
      <link>https://dev.to/daniel_wilsonkemp_62b88a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daniel_wilsonkemp_62b88a"/>
    <language>en</language>
    <item>
      <title>Lifted Sign: a self-hostable e-signature server that runs on nothing but SQLite</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Thu, 23 Jul 2026 08:37:23 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/lifted-sign-a-self-hostable-e-signature-server-that-runs-on-nothing-but-sqlite-3898</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/lifted-sign-a-self-hostable-e-signature-server-that-runs-on-nothing-but-sqlite-3898</guid>
      <description>&lt;p&gt;Most e-signature tools send signatures. Very few let you &lt;em&gt;own&lt;/em&gt; the whole thing — the documents, the signers, and the audit trail — on your own hardware. We built &lt;strong&gt;Lifted Sign&lt;/strong&gt; to be the one you can self-host, and to be genuinely boring to run: it boots on nothing but SQLite.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/LiftedHoldings/lifted-sign" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-sign&lt;/a&gt; (AGPL-3.0)&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch, honestly
&lt;/h2&gt;

&lt;p&gt;Upload a PDF, place fields, add signers, send single-use signing links. Each completed document is cryptographically sealed and ships with a &lt;strong&gt;Certificate of Completion&lt;/strong&gt; — the audit trail of signer identities, timestamps, IP addresses, and consent records that ESIGN and UETA actually call for.&lt;/p&gt;

&lt;p&gt;The difference is where all of that lives: your infrastructure, not a vendor's cloud. Data residency and air-gapped deploys are the default, not an enterprise tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite by default
&lt;/h2&gt;

&lt;p&gt;This is the part I'm happiest with. Lifted Sign boots with zero external services — one secret, and you're signing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SIGN_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 48&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 ghcr.io/liftedholdings/lifted-sign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt; and you have a working signing server. &lt;code&gt;SIGN_SECRET&lt;/code&gt; is the only required setting; every login session, signer-access cookie, and one-time code is keyed off it, so the server fails closed and loud if it's missing, too short, or a placeholder. Point it at Postgres with a single env var when you outgrow a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real PAdES certification, not a flattened image
&lt;/h2&gt;

&lt;p&gt;A lot of "e-signature" ends up being a signature image burned into a PDF. Lifted Sign gives completed PDFs a &lt;strong&gt;PKCS#7 / PAdES&lt;/strong&gt; digital signature that any PDF reader can verify and that breaks visibly if the file is altered. No signing certificate installed yet? It falls back to a tamper-evident AES-integrity seal, so a completed document is never left unsealed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How signing works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare.&lt;/strong&gt; Drop in a PDF, place signature/date/text/checkbox fields by anchor, add signers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send.&lt;/strong&gt; Each signer gets a single-use link. They review, hit the ESIGN/UETA consent gate, and sign in the browser — no account required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seal.&lt;/strong&gt; Once everyone signs, the PDF is sealed with a PAdES certification signature and a Certificate of Completion. Download both, or pull them via the API.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Developer-first
&lt;/h2&gt;

&lt;p&gt;There's a clean REST API under &lt;code&gt;/api/mysign/*&lt;/code&gt; for creating envelopes, adding signers, placing fields, sending, reminding, voiding, and downloading sealed PDFs and certificates. The in-app &lt;code&gt;/developers&lt;/code&gt; page documents the endpoints and serves an OpenAPI spec.&lt;/p&gt;

&lt;p&gt;Ready-to-use, dependency-free clients are vendored in the repo — a Python client (3.8+, standard library only) and a Node client (18+, built-in &lt;code&gt;fetch&lt;/code&gt;/&lt;code&gt;FormData&lt;/code&gt;). Both are &lt;strong&gt;MIT-licensed&lt;/strong&gt; even though the server is AGPL, so integrating against Lifted Sign never touches your application's licensing. Point either client's &lt;code&gt;base_url&lt;/code&gt; at your own install and drive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build from source
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/LiftedHoldings/lifted-sign.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lifted-sign
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import secrets; print('SIGN_SECRET=' + secrets.token_urlsafe(48))"&lt;/span&gt;  &lt;span class="c"&gt;# put this in .env&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; sign        &lt;span class="c"&gt;# or: docker compose up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why AGPL, and why free
&lt;/h2&gt;

&lt;p&gt;Self-hosting is GA and &lt;strong&gt;free forever&lt;/strong&gt; under the AGPL — no seat limits, no volume policing, no "contact us." The license is the only agreement. Passwordless email magic-link sign-in works out of the box; SMTP, Postgres, Google/phone sign-in, and PAdES certificates are optional add-ons you switch on by env var as you grow.&lt;/p&gt;

&lt;p&gt;If you'd rather not run it, there's a managed instance in free open beta at &lt;a href="https://sign.liftedholdings.com" rel="noopener noreferrer"&gt;https://sign.liftedholdings.com&lt;/a&gt; — same software, TLS and backups handled — but the self-hosted path stays free regardless.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo + docs:&lt;/strong&gt; &lt;a href="https://github.com/LiftedHoldings/lifted-sign" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-sign&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture write-up&lt;/strong&gt; (the ESIGN/UETA model, the three-runtime design, the persistence and security layers) lives in &lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built by&lt;/strong&gt; &lt;a href="https://liftedholdings.com" rel="noopener noreferrer"&gt;Lifted Holdings&lt;/a&gt;; see also &lt;a href="https://liftedpayments.com" rel="noopener noreferrer"&gt;Lifted Payments&lt;/a&gt; for interchange-plus merchant services with 3-D Secure card processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a debut open-source project, so contributors are what turn it into a community — there are &lt;code&gt;good first issue&lt;/code&gt; labels waiting. Bug reports, docs, tests, and code all welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>security</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Lifted ShipKit: open-source multi-carrier shipping labels with forced 3-D Secure</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Thu, 23 Jul 2026 08:36:26 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/lifted-shipkit-open-source-multi-carrier-shipping-labels-with-forced-3-d-secure-5dd1</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/lifted-shipkit-open-source-multi-carrier-shipping-labels-with-forced-3-d-secure-5dd1</guid>
      <description>&lt;p&gt;Shipping labels are one of those features that look trivial until you actually build them. You need a carrier-rating API, a payment step, PCI scope for the card data, and — the part nobody warns you about — a fraud problem, because labels bought with stolen cards are a top chargeback category. Real goods, shipped fast, disputed weeks later.&lt;/p&gt;

&lt;p&gt;We open-sourced &lt;strong&gt;Lifted ShipKit&lt;/strong&gt; to collapse that whole stack into a few lines of code. It's MIT-licensed, self-hostable, and it ships with a drop-in checkout widget that forces 3-D Secure on every card charge.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/LiftedHoldings/lifted-shipkit" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-shipkit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;ShipKit is a Kotlin/JVM backend plus a dependency-free browser widget. The backend wraps &lt;a href="https://www.easypost.com/" rel="noopener noreferrer"&gt;EasyPost&lt;/a&gt; for real multi-carrier shipping — address verification, live USPS/UPS/FedEx rate compare, SmartRates, label purchase, batch, scan forms, customs, and tracking webhooks — and adds a payment layer on top of it. The widget is a UMD global (&lt;code&gt;window.ShipKit&lt;/code&gt;), so it mounts in plain HTML, React, Vue, Svelte, or anything else with a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag and no build step.&lt;/p&gt;

&lt;p&gt;The part I want to talk about is the payment layer, because that's the design decision that makes ShipKit different from "just call EasyPost yourself."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why forced 3-D Secure is the whole point
&lt;/h2&gt;

&lt;p&gt;Most teams that add shipping wire a rating API straight to a payment processor and then quietly own two things they didn't sign up for: the PCI scope for card data, and the chargeback liability when a fraudster buys a label with a stolen card.&lt;/p&gt;

&lt;p&gt;ShipKit forces &lt;strong&gt;3-D Secure&lt;/strong&gt; on every card charge. The issuer authenticates the buyer &lt;em&gt;before&lt;/em&gt; the label prints, which shifts fraud-and-chargeback liability off the merchant. Card data goes through hosted fields, so it stays out of your PCI scope. This isn't an enterprise upsell or a config flag you can forget to turn on — it's always on, by construction.&lt;/p&gt;

&lt;p&gt;For a product whose fraud profile is "real goods, shipped immediately, disputed later," making authentication non-optional is the correct default.&lt;/p&gt;

&lt;h2&gt;
  
  
  60-second try
&lt;/h2&gt;

&lt;p&gt;No account, no keys — the published Docker image comes up immediately (shipping and payment features return &lt;code&gt;503&lt;/code&gt; until you supply an EasyPost key and 3DS credentials):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SHIPKIT_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8080 &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 ghcr.io/liftedholdings/lifted-shipkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drop it into your own checkout
&lt;/h2&gt;

&lt;p&gt;The headline use case is keeping your app and your checkout and adding ShipKit for the rate-compare -&amp;gt; pay -&amp;gt; print-label step. Mount a node, point it at your backend, wire a callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ship"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/shipkit.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;ShipKit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ship&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pk_live_your_publishable_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// publishable pk_… key — safe in the browser&lt;/span&gt;
    &lt;span class="na"&gt;onPurchase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;trackingCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;labelUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// hand the finished label back to your order flow&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keys come in two scopes: the browser widget uses a &lt;strong&gt;publishable&lt;/strong&gt; &lt;code&gt;pk_…&lt;/code&gt; key (the backend confines it to the customer flow), while your server calls use a &lt;strong&gt;secret&lt;/strong&gt; &lt;code&gt;sk_…&lt;/code&gt; key that never appears in client code. Every surface restyles with &lt;code&gt;--sk-*&lt;/code&gt; CSS variables — no &lt;code&gt;!important&lt;/code&gt;, no build step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-host it fully
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/LiftedHoldings/lifted-shipkit.git
&lt;span class="nb"&gt;cd &lt;/span&gt;shipkit
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env                       &lt;span class="c"&gt;# EASYPOST_API_KEY + your 3DS keys&lt;/span&gt;
./gradlew build                            &lt;span class="c"&gt;# Kotlin 2.0.21, JVM 17&lt;/span&gt;
./gradlew shipkitKeygen &lt;span class="nt"&gt;-Plabel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-store   &lt;span class="c"&gt;# mint an API key&lt;/span&gt;
./gradlew run                              &lt;span class="c"&gt;# API + widget on :8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every setting is read from the environment. Two optional, env-driven hooks let a platform operate a fleet of instances from a control plane — a bearer-guarded &lt;code&gt;POST /api/config/markup&lt;/code&gt; to update the shipping markup remotely, and a fire-and-forget &lt;code&gt;label.purchased&lt;/code&gt; webhook (carrier cost and buyer charge in integer cents) — both &lt;strong&gt;off by default&lt;/strong&gt; when their tokens are unset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping as a profit center
&lt;/h2&gt;

&lt;p&gt;One design choice worth calling out for anyone building a store: whoever owns the payments account owns the markup. ShipKit prices every label at the carrier rate &lt;strong&gt;plus your own percentage and fixed fee&lt;/strong&gt; (&lt;code&gt;percentage_markup&lt;/code&gt; + &lt;code&gt;fixed_fee_cents&lt;/code&gt;), applied server-side and shown at checkout. Self-host it and that margin is 100% yours on every label.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open source it
&lt;/h2&gt;

&lt;p&gt;Because the honest version of this tool is one you can read, fork, and run yourself. MIT license, dependency-free frontend, clean modular backend, no per-label SaaS fee when you self-host. If you'd rather not run infra, there's a managed tier and a 3-D Secure merchant-account tier — but the code, the widget, and self-hosting stay free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://liftedholdings.com/shippingtool" rel="noopener noreferrer"&gt;https://liftedholdings.com/shippingtool&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo + docs:&lt;/strong&gt; &lt;a href="https://github.com/LiftedHoldings/lifted-shipkit" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-shipkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The payments side&lt;/strong&gt; (interchange-plus merchant services with 3-D Secure, built by the same team): &lt;a href="https://liftedpayments.com" rel="noopener noreferrer"&gt;https://liftedpayments.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build something with it, or you want ShipKit woven into your own framework, we'd genuinely like to hear about it. PRs and issues welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>kotlin</category>
      <category>payments</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
