<?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: Alex Tomita</title>
    <description>The latest articles on DEV Community by Alex Tomita (@rezelco).</description>
    <link>https://dev.to/rezelco</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2066293%2Fc4657a9c-9540-4992-b5e2-e98cd97b0d17.jpg</url>
      <title>DEV Community: Alex Tomita</title>
      <link>https://dev.to/rezelco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rezelco"/>
    <language>en</language>
    <item>
      <title>SayFight - A Real-Time Voice-Controlled Party Game</title>
      <dc:creator>Alex Tomita</dc:creator>
      <pubDate>Mon, 28 Jul 2025 01:36:27 +0000</pubDate>
      <link>https://dev.to/rezelco/sayfight-a-real-time-voice-controlled-party-game-1hgh</link>
      <guid>https://dev.to/rezelco/sayfight-a-real-time-voice-controlled-party-game-1hgh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/assemblyai-2025-07-16"&gt;AssemblyAI Voice Agents Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SayFight&lt;/strong&gt; is a chaotic, real-time, multiplayer party game where players shout funny 3-word phrases to activate their character's actions in fast-paced mini-games. The game uses &lt;strong&gt;AssemblyAI's Universal-Streaming API&lt;/strong&gt; to transcribe speech in real time, allowing players to say phrases like “banana punch blast” or “snake sneak roll” to score points or move their team forward.&lt;/p&gt;

&lt;p&gt;This project is built for the &lt;strong&gt;Real-Time Performance&lt;/strong&gt; prompt, showcasing how AssemblyAI enables lightning-fast, voice-driven interactions for games and other real-time apps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt;: This is a &lt;strong&gt;proof of concept&lt;/strong&gt;. Due to time constraints, the project focuses on core functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time voice command recognition
&lt;/li&gt;
&lt;li&gt;Phrase-to-action triggering
&lt;/li&gt;
&lt;li&gt;Multiplayer game loop
&lt;/li&gt;
&lt;li&gt;AssemblyAI integration via WebSocket streaming
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The current version includes &lt;strong&gt;two simple game modes&lt;/strong&gt;, each designed to demonstrate the real-time voice interaction loop. While limited in scope, SayFight lays the foundation for a more fully featured voice-controlled party game in future iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🎮 &lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://sayfight.com" rel="noopener noreferrer"&gt;https://sayfight.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📸 &lt;strong&gt;Screenshots&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqto71z2ntnlhc55yyoq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqto71z2ntnlhc55yyoq.png" alt="Main Page" width="800" height="553"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxghy6qi89e9ivrsszxrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxghy6qi89e9ivrsszxrx.png" alt="Player Join Screen" width="731" height="699"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyt6vnakhy5w4tx5aw2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyt6vnakhy5w4tx5aw2s.png" alt="Host Screen" width="800" height="588"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46jk07yu6f0utrnhtx9h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46jk07yu6f0utrnhtx9h.png" alt="Wait Screen" width="800" height="521"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypaye8r8zt8gm4htwgeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypaye8r8zt8gm4htwgeu.png" alt="Player Screen" width="610" height="755"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrgv3915eh1tn24hd9a2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrgv3915eh1tn24hd9a2.png" alt="Tug of War" width="800" height="592"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4nl51hesp28q4sb6bhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4nl51hesp28q4sb6bhi.png" alt="Voice Racer" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/rezelco/sayfight.com" rel="noopener noreferrer"&gt;https://github.com/rezelco/sayfight.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Implementation &amp;amp; AssemblyAI Integration
&lt;/h2&gt;

&lt;p&gt;SayFight uses AssemblyAI's &lt;strong&gt;Universal Streaming API&lt;/strong&gt; to transcribe player voice input with low latency. The entire gameplay loop is built around fast transcription, text matching, and synchronized game logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛠 Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 14 (App Router) with Tailwind CSS + shadcn/ui, HTML5 Canvas for game visuals
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js + TypeScript with Socket.io for real-time multiplayer
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice Capture&lt;/strong&gt;: Web Audio API with MediaStream (not WebRTC – direct audio capture)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice Streaming&lt;/strong&gt;: AssemblyAI Universal Streaming WebSocket API
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio Processing&lt;/strong&gt;: 16kHz PCM audio format&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔁 Voice-to-Action Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Player joins room and grants microphone permission&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser captures audio using MediaStream API
&lt;/li&gt;
&lt;li&gt;Audio is processed at 16kHz sample rate with echo cancellation
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audio is streamed to backend via Socket.io&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PCM audio chunks sent as &lt;code&gt;ArrayBuffer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Not using WebRTC — direct Socket.io binary transport
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backend relays audio to AssemblyAI&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each player has a dedicated AssemblyAI WebSocket session
&lt;/li&gt;
&lt;li&gt;Audio is forwarded in real-time to AssemblyAI’s streaming endpoint
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transcribed text is received with confidence scores&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AssemblyAI returns partial and final transcripts
&lt;/li&gt;
&lt;li&gt;Only final transcripts with &amp;gt;0.7 confidence are processed
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Command extraction and matching&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text is matched against the player’s assigned phrase (e.g., “pandas hack wifi”)
&lt;/li&gt;
&lt;li&gt;Scoring system:

&lt;ul&gt;
&lt;li&gt;Full phrase match: 2× points
&lt;/li&gt;
&lt;li&gt;Partial phrase: 1.5× points
&lt;/li&gt;
&lt;li&gt;Trigger word only: 1× point
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total latency target:&lt;/strong&gt; ~300–400ms
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Game state update and broadcast&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server updates authoritative game state at 60 FPS
&lt;/li&gt;
&lt;li&gt;State changes broadcast to all clients via Socket.io
&lt;/li&gt;
&lt;li&gt;No client-side prediction needed due to voice command delay envelope&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  🎯 Key Technical Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🐼 &lt;strong&gt;Phrase-based matching&lt;/strong&gt;: 100+ unique animal-themed phrases with homophone avoidance
&lt;/li&gt;
&lt;li&gt;🏆 &lt;strong&gt;Multi-tier scoring&lt;/strong&gt;: Rewards for full phrase vs partial or single-word triggers
&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;Debug visualization&lt;/strong&gt;: Host UI shows expected vs received transcription for tuning
&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Persistent rooms&lt;/strong&gt;: Players can reconnect and resume if disconnected
&lt;/li&gt;
&lt;li&gt;👥 &lt;strong&gt;Team-based audio&lt;/strong&gt;: Tug-of-war mode assigns unique phrases to each team member&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streaming audio reliably is tricky&lt;/strong&gt;: WebRTC wasn’t ideal for this use case—direct capture with Socket.io worked better for latency and control.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phrase design matters&lt;/strong&gt;: Carefully crafted, phonetically distinct phrases dramatically improved transcription accuracy.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast &amp;gt; perfect&lt;/strong&gt;: A snappy game loop with ~300ms latency was more fun than waiting for ultra-precise recognition.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AssemblyAI impressed&lt;/strong&gt;: The API was reliable, fast, and handled noisy party environments better than expected.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Future Plans
&lt;/h2&gt;

&lt;p&gt;If I continue this project, here’s what’s next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎮 &lt;strong&gt;More game modes&lt;/strong&gt; like rapid-fire trivia, fighting, or co-op modes &lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;Flexible NLU&lt;/strong&gt; with fuzzy matching and intent-based phrase recognition
&lt;/li&gt;
&lt;li&gt;🌍 &lt;strong&gt;Multilingual support&lt;/strong&gt; via AssemblyAI’s language capabilities
&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;User-generated content&lt;/strong&gt;: Let players design their own phrases and game types
&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;Mobile-first UI/UX&lt;/strong&gt; with animated avatars, emotes, and voice indicators
&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Noise resilience&lt;/strong&gt; and support for simultaneous voice inputs across teams&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks to &lt;a href="https://www.assemblyai.com/" rel="noopener noreferrer"&gt;AssemblyAI&lt;/a&gt; and &lt;a href="https://dev.to"&gt;DEV&lt;/a&gt; for hosting this challenge — building this was a blast!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>assemblyaichallenge</category>
      <category>ai</category>
      <category>api</category>
    </item>
    <item>
      <title>PurifyPDF – A Privacy-First PDF Sanitizer in Your Inbox</title>
      <dc:creator>Alex Tomita</dc:creator>
      <pubDate>Mon, 09 Jun 2025 04:35:14 +0000</pubDate>
      <link>https://dev.to/rezelco/purifypdf-a-privacy-first-pdf-sanitizer-in-your-inbox-4i85</link>
      <guid>https://dev.to/rezelco/purifypdf-a-privacy-first-pdf-sanitizer-in-your-inbox-4i85</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PurifyPDF&lt;/strong&gt; is a privacy-first document sanitizer that lives in your inbox. Users simply email a PDF to &lt;code&gt;clean@purifypdf.xyz&lt;/code&gt;, and moments later receive a cleaned version — stripped of hidden metadata, embedded scripts, and potentially unsafe content.&lt;/p&gt;

&lt;p&gt;There’s no app to install, no interface to learn — just send, wait, receive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PurifyPDF does not retain files longer than necessary.&lt;/strong&gt; Uploaded documents are stored temporarily for processing and purged after the retention period. User metadata is stored only for usage tracking and verification.&lt;/p&gt;

&lt;p&gt;The service prioritizes security by design: PDFs are converted into image-only documents before being rebuilt, ensuring that any embedded scripts, file attachments, hyperlinks, or other potentially harmful elements are completely removed.&lt;/p&gt;

&lt;p&gt;PurifyPDF is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy-conscious professionals
&lt;/li&gt;
&lt;li&gt;Journalists, lawyers, or auditors
&lt;/li&gt;
&lt;li&gt;Anyone sharing or receiving documents from unknown sources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;To test the service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send an email to &lt;code&gt;clean@purifypdf.xyz&lt;/code&gt; with the subject: &lt;code&gt;register&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You’ll receive a one-time verification link. Click it to verify your email
&lt;/li&gt;
&lt;li&gt;Send another email to &lt;code&gt;clean@purifypdf.xyz&lt;/code&gt; with a PDF attached
&lt;/li&gt;
&lt;li&gt;Wait about a minute and receive a sanitized version in your inbox&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;PDF size limit:&lt;/strong&gt; 5 MB&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Recommended page limit:&lt;/strong&gt; 25 pages&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Usage limit:&lt;/strong&gt; Each verified user can sanitize up to 3 PDFs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Judges:&lt;/strong&gt; If you need your usage limit increased for evaluation, please email &lt;code&gt;support@rezelco.com&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/rezelco/purifypdf-n8n" rel="noopener noreferrer"&gt;https://github.com/rezelco/purifypdf-n8n&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/rezelco" rel="noopener noreferrer"&gt;
        rezelco
      &lt;/a&gt; / &lt;a href="https://github.com/rezelco/purifypdf-n8n" rel="noopener noreferrer"&gt;
        purifypdf-n8n
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Email-based PDF sanitizer using Postmark, PDF.co, Airtable, and n8n
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;PurifyPDF – Email-Based PDF Sanitizer&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;PurifyPDF&lt;/strong&gt; is a privacy-first PDF sanitization workflow built using &lt;a href="https://n8n.io" rel="nofollow noopener noreferrer"&gt;n8n&lt;/a&gt;, &lt;a href="https://postmarkapp.com" rel="nofollow noopener noreferrer"&gt;Postmark&lt;/a&gt;, &lt;a href="https://pdf.co" rel="nofollow noopener noreferrer"&gt;PDF.co&lt;/a&gt;, and &lt;a href="https://airtable.com" rel="nofollow noopener noreferrer"&gt;Airtable&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Users send a PDF as an email attachment to &lt;code&gt;clean@yourdomain.com&lt;/code&gt;, and the system returns a sanitized version, free of metadata, embedded scripts, and potentially unsafe content.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Send an email to &lt;code&gt;clean@yourdomain.com&lt;/code&gt; with the subject: &lt;code&gt;register&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You’ll receive a verification link. Click to activate your email&lt;/li&gt;
&lt;li&gt;Send another email with a PDF attached&lt;/li&gt;
&lt;li&gt;Receive a sanitized version in your inbox&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Limits&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Max PDF size: 5 MB&lt;/li&gt;
&lt;li&gt;Recommended page count: 25&lt;/li&gt;
&lt;li&gt;Each user may sanitize up to 3 files&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🧰 Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; – Orchestrates workflow logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark&lt;/strong&gt; – Handles incoming and outgoing emails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF.co&lt;/strong&gt; – Converts and rebuilds PDFs for sanitization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airtable&lt;/strong&gt; – Tracks users, limits, and verification&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🔒 Privacy&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;PurifyPDF never stores files permanently. All PDFs are purged automatically after one hour. Only metadata…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/rezelco/purifypdf-n8n" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This repository includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The full sanitized n8n workflow (&lt;code&gt;purifypdf.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;MIT License and setup documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the &lt;a href="https://github.com/rezelco/purifypdf-n8n#readme" rel="noopener noreferrer"&gt;README&lt;/a&gt; for installation steps, required credentials, and detailed architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;PurifyPDF is built entirely in &lt;a href="https://n8n.io" rel="noopener noreferrer"&gt;n8n&lt;/a&gt;, using no-code and low-code logic to orchestrate document processing, verification, and email flows.&lt;/p&gt;

&lt;p&gt;The goal was to create something that feels &lt;em&gt;invisible&lt;/em&gt; to users — no apps, no dashboards — just smart automation behind a simple email address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; – Manages the full automation pipeline, including webhook ingestion, token generation, user tracking, and email sending.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark&lt;/strong&gt; – Powers both inbound and outbound email. Incoming attachments are parsed through webhooks, and outgoing messages are branded, responsive, and fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF.co&lt;/strong&gt; – Sanitizes documents by converting each page to an image and rebuilding a new, clean PDF — free of metadata, scripts, and embedded content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airtable&lt;/strong&gt; – Stores user records, verification tokens, timestamps, and usage limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript (Function nodes)&lt;/strong&gt; – Used for logic branching, token generation, rate limiting, and retry logic inside the n8n flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Design Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Email-first UX&lt;/strong&gt; – Every interaction happens through email. No UI required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-click user registration&lt;/strong&gt; – Email-based verification with expiring tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry handling&lt;/strong&gt; – Long-running PDF conversions use polling + wait loops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage limits&lt;/strong&gt; – Users can sanitize up to 3 PDFs unless upgraded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security &amp;amp; privacy&lt;/strong&gt; – Files are stored temporarily and purged after processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  n8n Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ieb8sb5l8sz4y20qb5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ieb8sb5l8sz4y20qb5o.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Chose Postmark
&lt;/h2&gt;

&lt;p&gt;Postmark made it easy to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receive emails and attachments via webhooks
&lt;/li&gt;
&lt;li&gt;Monitor all incoming and outgoing email activity — incredibly helpful for debugging and delivery tracking
&lt;/li&gt;
&lt;li&gt;Seamlessly integrate with &lt;strong&gt;n8n&lt;/strong&gt;, making it easy to trigger flows, parse messages, and send responses without glue code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenge Reflection
&lt;/h2&gt;

&lt;p&gt;This project started with a real, everyday problem at home. I found out my wife keeps a separate laptop that she uses &lt;em&gt;only&lt;/em&gt; to open vendor invoices. After opening them, she prints the files, walks them to a second computer, and scans them back in — all to avoid the risk of opening untrusted PDFs on her main machine.&lt;/p&gt;

&lt;p&gt;It was a clever workaround, but it got me thinking: &lt;em&gt;in a world full of automation tools, why are we still physically printing documents just to feel safe?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That idea became the foundation for PurifyPDF. I wanted to build something simple, trustworthy, and accessible — a tool that requires no apps, no learning curve, and no new habits. If someone like my wife could use it without friction, that meant it could help a lot of other people too.&lt;/p&gt;

&lt;p&gt;The Postmark Challenge was the perfect setting to bring it to life. Postmark made handling inbound and outbound email effortless, and pairing it with n8n and PDF.co gave me the flexibility to build a secure, responsive service without writing a backend from scratch.&lt;/p&gt;

&lt;p&gt;This was one of the most rewarding projects I've worked on. It solves a real problem, feels invisible to the user, and showcases just how powerful email-driven automation can be.&lt;/p&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;MIT License&lt;/p&gt;

&lt;p&gt;The name &lt;strong&gt;PurifyPDF&lt;/strong&gt; and associated branding (e.g., logo, domain name) are not covered by the MIT license and may not be used without permission.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
  </channel>
</rss>
