Skip to content

feat: iOS audio sharesheet via Tailscale HTTPS + iOS Shortcut #1

Description

Problem Statement

Scott records patient care audio on his PLAUD device and phone, then has to manually transfer files to his Mac before EMScribe can process them. There is no way to send an audio file directly from his iPhone to EMScribe — every upload requires sitting at the Mac. This friction delays documentation and breaks the mobile-first workflow he needs in the field.

Solution

Two changes working together:

  1. Enable HTTPS on the EMScribe server via a Tailscale TLS certificate and uvicorn SSL configuration, making the server reachable from an iPhone over Tailscale at https://macbook-pro.ghoul-tegus.ts.net:911.

  2. An iOS Shortcut that appears in the iOS sharesheet and silently POSTs any shared audio file to the existing /encounters/ upload endpoint — with a success banner on completion and an error alert if the server is unreachable.

No new server endpoints are needed. The existing multipart upload endpoint accepts the file as-is. The Shortcut fires and returns control to the previous app immediately — EMScribe does not open.

User Stories

  1. As a paramedic finishing a call, I want to share an audio file from my PLAUD app directly to EMScribe, so that processing starts while I'm still at the scene without sitting down at my Mac.
  2. As a user, I want to share audio files from Voice Memos, the Files app, or any other iOS app to EMScribe, so that I'm not limited to one recording source.
  3. As a user, I want the share action to return me immediately to the app I was using, so that I can continue my workflow without interruption.
  4. As a user, I want a confirmation banner when the upload succeeds, so that I know EMScribe received the file.
  5. As a user, I want a clear error alert when the upload fails (e.g., Mac asleep or Tailscale disconnected), so that I know to retry rather than assuming the file was lost.
  6. As a user, I want the server to be reachable from my iPhone over Tailscale with a valid HTTPS certificate, so that the connection is secure and trusted by iOS.
  7. As a user, I want the SSL certificate paths to be configurable via environment variables, so that I can point the server at new cert files without modifying code.
  8. As a user, I want the server to start without SSL when no cert paths are configured, so that local development and existing workflows are unaffected.
  9. As a user, I want the Shortcut to accept any audio file type, so that it works regardless of which recording app I use.
  10. As a user, I want the Shortcut to send the file to EMScribe silently in the background, so that EMScribe does not open or interrupt what I'm doing on my phone.
  11. As a user, I want step-by-step guidance for building the iOS Shortcut myself, so that I can set it up without needing development tools or special software.

Implementation Decisions

Modules Modified

server/config.py

  • Add two new optional string settings: ssl_certfile (default empty string) and ssl_keyfile (default empty string).
  • Settings are read from environment variables (SSL_CERTFILE, SSL_KEYFILE) following the existing Pydantic Settings pattern.
  • Empty string means SSL is disabled; non-empty means SSL is enabled.

run_server.py

  • After loading settings, check if both ssl_certfile and ssl_keyfile are non-empty.
  • If so, pass them as ssl_certfile and ssl_keyfile kwargs to uvicorn.run().
  • If not, run without SSL (current behavior preserved exactly).
  • No change to host, port, or reload configuration.

iOS Shortcut (manual setup — no code)

  • Shortcut input type: Files (audio), sourced from the sharesheet.
  • Action: "Get Contents of URL" with method POST, multipart/form-data body, one field named file set to the shortcut input.
  • URL: https://macbook-pro.ghoul-tegus.ts.net:911/encounters/
  • On HTTP 201: show notification "EMScribe: Upload started".
  • On any other result or network failure: show alert "Could not reach EMScribe. Is your Mac on?"
  • A step-by-step setup guide will be written to docs/ios-shortcut-setup.md.

Architectural Decisions

  • No new server endpoint. The existing POST /encounters/ endpoint accepts multipart/form-data with a file field — exactly what the Shortcut sends.
  • Tailscale for network access, not Cloudflare Tunnel. Audio (PHI) must not transit third-party infrastructure. Tailscale is end-to-end encrypted and already installed on both devices.
  • tailscale cert is used to obtain a valid Let's Encrypt certificate for macbook-pro.ghoul-tegus.ts.net. This is a manual prerequisite step, not automated by the server.
  • Conditional SSL in uvicorn. SSL is opt-in via env vars so local development continues to work without certs.
  • iOS Shortcut, not Web Share Target PWA. Web Share Target always foregrounds the PWA on iOS — incompatible with the silent background upload requirement.

SSL Environment Variables

Variable Default Description
SSL_CERTFILE "" Path to TLS certificate file (e.g. output of tailscale cert)
SSL_KEYFILE "" Path to TLS private key file

Prerequisites (manual, one-time)

  1. tailscale cert macbook-pro.ghoul-tegus.ts.net — generates cert and key files.
  2. Set SSL_CERTFILE and SSL_KEYFILE env vars to the generated file paths.
  3. Restart the EMScribe server.
  4. Build the iOS Shortcut using the guide in docs/ios-shortcut-setup.md.

Testing Decisions

What makes a good test: Test external behavior and config contracts — not internal implementation. A test for SSL config should verify that the settings fields exist with the correct defaults and can be overridden, not that uvicorn was called with specific kwargs.

Modules with tests:

  • server/config.py — Add to tests/server/test_config.py (existing file). Two new tests: one asserts ssl_certfile and ssl_keyfile default to empty string; one asserts they can be overridden via constructor. Prior art: test_default_settings() and test_settings_override() in the same file.

Not tested:

  • run_server.py — The uvicorn launch logic is a thin entry point; integration-testing it would require spawning a subprocess. The config contract (tested above) is the meaningful assertion.
  • The iOS Shortcut — manual setup, no automated test surface.

Out of Scope

  • Push notifications — The server will not send completion notifications to the iPhone. Scott will check EMScribe manually or open the app. This is planned as a follow-on feature using iOS Web Push.
  • Web Share Target / PWA manifest changes — Not used because it foregrounds the app, which conflicts with the fire-and-forget requirement.
  • Cloudflare Tunnel or any third-party network proxy — Excluded on HIPAA grounds (PHI in audio would transit third-party infrastructure).
  • Authentication / API keys on the /encounters/ endpoint — The server is only reachable via Tailscale (private network), so no additional auth is added in this pass.
  • Automatic cert renewaltailscale cert renewal is handled by Tailscale; the server just reads the files on startup.
  • Multiple simultaneous file uploads from the Shortcut — The Shortcut sends one file per invocation.

Further Notes

  • tailscale cert on macOS writes files to the current working directory unless a path is specified. Scott should run it from a stable directory (e.g. ~/certs/) and set env vars accordingly.
  • The Tailscale HTTPS cert is only valid while the machine is enrolled in the Tailscale network. Cert expiry is managed automatically by Tailscale.
  • The server currently runs on port 911. With SSL enabled, the URL becomes https://macbook-pro.ghoul-tegus.ts.net:911 (non-standard port, which is fine for a private tool).
  • This feature is a prerequisite for future mobile improvements: installing the PWA on iPhone, and eventually Web Push notifications for processing completion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions