<?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: Simple Memo</title>
    <description>The latest articles on DEV Community by Simple Memo (@simple_memo).</description>
    <link>https://dev.to/simple_memo</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%2F3919840%2Ff3e34759-885a-4e5e-9959-57c82a1a9c45.png</url>
      <title>DEV Community: Simple Memo</title>
      <link>https://dev.to/simple_memo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/simple_memo"/>
    <language>en</language>
    <item>
      <title>I would choose the task-tool weights before the tool</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 08 Sep 2026 02:58:53 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-would-choose-the-task-tool-weights-before-the-tool-3a9c</link>
      <guid>https://dev.to/simple_memo/i-would-choose-the-task-tool-weights-before-the-tool-3a9c</guid>
      <description>&lt;p&gt;I would choose the weights before choosing the task tool. Here is a worked example with five formats and six criteria. Every score below is illustrative: this is a decision model, not a benchmark or a report of tools I tested for a month.&lt;/p&gt;

&lt;p&gt;That distinction is the point of the exercise. A decimal result can make a preference look like a measurement. I want the arithmetic to expose my assumptions, including the assumptions that would make a different format win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I separate requirements a tool must satisfy from preferences that can be traded off.&lt;/li&gt;
&lt;li&gt;I calculate several complete weight profiles instead of treating one ranking as a recommendation.&lt;/li&gt;
&lt;li&gt;I would test the weakest assumption before migrating a backlog.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What decision am I actually making?
&lt;/h2&gt;

&lt;p&gt;I would scope this decision to a personal queue of next actions. A shared release plan, a reference archive, and a scratchpad have different jobs. Combining them into one question makes the scoring ambiguous before the first number is entered.&lt;/p&gt;

&lt;p&gt;My candidate formats are a task list, a kanban board, an issue tracker, a dated text file, and paper. These names describe possible arrangements, not particular products. A text file can have a sophisticated interface. A board can accept captures through a shortcut. A task app can export readable files. I would score the actual arrangement I intend to use, rather than attach a permanent score to a category.&lt;/p&gt;

&lt;p&gt;Before assigning weights, I would write down any hard requirements. If a project requires shared ownership and an audit trail, a candidate without those capabilities fails the initial screen. Excellent capture speed cannot compensate for missing access controls. Likewise, a workflow that must operate without connectivity needs a real offline test. It should not receive a low sync score and quietly remain in the running.&lt;/p&gt;

&lt;p&gt;That gives me two separate decisions: is the candidate eligible, and how attractive is it among the eligible choices? The weighted table answers only the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which six criteria would I score?
&lt;/h2&gt;

&lt;p&gt;For this example I use capture, sync, portability, retrieval, review, and structure. Higher always means better. In particular, a high capture score means &lt;em&gt;less&lt;/em&gt; friction; otherwise the column name can accidentally reverse the calculation.&lt;/p&gt;

&lt;p&gt;Capture covers the steps between deciding to record an item and confirming it was saved. Sync covers the behavior across the devices in scope. Portability covers exporting and reopening the information I actually need, including dates or attachments when relevant. Retrieval covers finding a known item. Review covers noticing unfinished work. Structure covers relationships such as dependencies, owners, and recurring tasks.&lt;/p&gt;

&lt;p&gt;I would define the scoring anchors before testing. For capture, a score of five might mean a prepared capture shortcut records the sample task without a classification choice. A score of one might mean several mandatory fields interrupt that same operation. Those are proposed anchors, not observations about any named application.&lt;/p&gt;

&lt;p&gt;Sync needs different anchors. I would distinguish an observed cross-device success from a recovery test after disconnection. A single successful transfer does not establish reliability. When I lack evidence, I would mark the criterion unknown and schedule a test instead of giving it a comfortable three.&lt;/p&gt;

&lt;p&gt;The example assumes every cell has a score so the arithmetic is readable. A real decision sheet should keep an evidence note alongside each cell. Otherwise the table preserves the number while losing the reason it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the illustrative scorecard say?
&lt;/h2&gt;

&lt;p&gt;Here are deliberately hypothetical scores. They are inputs for demonstrating the method, and should be replaced before selecting a real tool.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Candidate arrangement&lt;/th&gt;
&lt;th&gt;Capture&lt;/th&gt;
&lt;th&gt;Sync&lt;/th&gt;
&lt;th&gt;Portability&lt;/th&gt;
&lt;th&gt;Retrieval&lt;/th&gt;
&lt;th&gt;Review&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task list&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kanban board&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Issue tracker&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dated text file&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paper&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a capture-focused profile, I assign weights of 40, 15, 15, 12, 10, and 8 percent in that column order. They sum to 100. Capture is the largest individual weight, but the other criteria together still account for 60 percent.&lt;/p&gt;

&lt;p&gt;The calculation is the sum of each score multiplied by its weight, divided by 100. For the dated text file, that is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = (5*40 + 4*15 + 5*15 + 3*12 + 2*10 + 2*8) / 100
      = 4.07
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results are 3.30 for the task list, 2.88 for the board, 3.30 for the tracker, 4.07 for the text file, and 2.70 for paper. The text arrangement leads under these assumptions despite scoring two on both review and structure.&lt;/p&gt;

&lt;p&gt;I would not interpret 4.07 as an objective quality score. Multiplying subjective ratings produces precise arithmetic, not precise knowledge. The distinction between a four and a five might be much less defensible than the two decimal places suggest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when I change the whole weight profile?
&lt;/h2&gt;

&lt;p&gt;Saying “make review important” is incomplete. Increasing one weight requires reducing others if the total is to stay at 100. Here are three fully specified profiles, using the same hypothetical scores.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profile&lt;/th&gt;
&lt;th&gt;Capture&lt;/th&gt;
&lt;th&gt;Sync&lt;/th&gt;
&lt;th&gt;Portability&lt;/th&gt;
&lt;th&gt;Retrieval&lt;/th&gt;
&lt;th&gt;Review&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Capture-focused&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review-focused&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structure-focused&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Candidate arrangement&lt;/th&gt;
&lt;th&gt;Capture-focused&lt;/th&gt;
&lt;th&gt;Review-focused&lt;/th&gt;
&lt;th&gt;Structure-focused&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task list&lt;/td&gt;
&lt;td&gt;3.30&lt;/td&gt;
&lt;td&gt;3.70&lt;/td&gt;
&lt;td&gt;3.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kanban board&lt;/td&gt;
&lt;td&gt;2.88&lt;/td&gt;
&lt;td&gt;4.00&lt;/td&gt;
&lt;td&gt;3.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Issue tracker&lt;/td&gt;
&lt;td&gt;3.30&lt;/td&gt;
&lt;td&gt;3.50&lt;/td&gt;
&lt;td&gt;4.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dated text file&lt;/td&gt;
&lt;td&gt;4.07&lt;/td&gt;
&lt;td&gt;2.90&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paper&lt;/td&gt;
&lt;td&gt;2.70&lt;/td&gt;
&lt;td&gt;2.50&lt;/td&gt;
&lt;td&gt;1.70&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The leader changes from text file to board to tracker. No candidate changed its features between those calculations. I changed the question being asked of the candidates.&lt;/p&gt;

&lt;p&gt;This is where I find the exercise useful: disagreement becomes inspectable. Someone who values review can object to my allocation of attention without having to claim my preferred format is universally bad. I can also see whether my chosen weights merely reward the tool I already wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which assumption should I test first?
&lt;/h2&gt;

&lt;p&gt;I would start with a high-weight score supported by weak evidence. In the capture-focused example, reducing the text file's capture score from five to three lowers its total by 0.80, from 4.07 to 3.27. The task list and issue tracker then both lead it at 3.30.&lt;/p&gt;

&lt;p&gt;That is a useful warning. If the convenient capture path exists only in my imagination, the apparent winner depends on an untested premise. I would set up that path and try it before moving any existing tasks.&lt;/p&gt;

&lt;p&gt;I would use the same small set of sample actions for each candidate: record an interrupted thought, retrieve an older item, notice an overdue action, reopen data on the second device, and export an item with its relevant context. I would record failures as well as completion times. Repeating a test under different conditions would give me more evidence than polishing the score descriptions.&lt;/p&gt;

&lt;p&gt;A short trial cannot prove ten-year durability or a universal failure rate. It can expose an inconvenient login, an export that omits something important, or a review screen I cannot use comfortably. I would keep those findings at the level the trial supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would I combine a capture file with a tracker?
&lt;/h2&gt;

&lt;p&gt;Possibly, but I would evaluate the combination as another candidate. Moving an item between tools adds a handoff, and a handoff can create duplicates or leave an item stranded. A two-tool arrangement should earn its score through the full path from capture to completion.&lt;/p&gt;

&lt;p&gt;I would specify which location owns the task after promotion and how I recognize the promotion later. Without that rule, the fast capture score can hide a slow reconciliation job. The model should include that job rather than stop at the pleasant first step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the highest total decide the migration?
&lt;/h2&gt;

&lt;p&gt;I would use it to choose a trial, not authorize a migration automatically. A close result calls for checking uncertain inputs. A failed hard requirement rules out a candidate regardless of its total. A substantial switching cost might justify staying with a satisfactory arrangement while testing an alternative on new work only.&lt;/p&gt;

&lt;p&gt;I would also save the original weights before the trial. Editing them afterward can be reasonable, but I want a note explaining what changed. That makes the document a decision journal instead of a retrospective justification.&lt;/p&gt;

&lt;h2&gt;
  
  
  When would I review the decision?
&lt;/h2&gt;

&lt;p&gt;I would choose a review date relative to the trial's start and list the signals that could trigger an earlier review. Repeatedly missing unfinished tasks would make me revisit review. Frequent movement into another system would make me revisit structure. A failed restore would reopen eligibility, not merely reduce portability by one point.&lt;/p&gt;

&lt;p&gt;The result I want is a small record of assumptions, evidence, and unresolved trade-offs. A ranked list without those ingredients is easy to copy and difficult to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open question for the comments
&lt;/h2&gt;

&lt;p&gt;Which criterion would receive your largest weight, and what concrete failure would make you lower a candidate's score on that criterion? I am especially interested in examples where testing changed the weights rather than just confirming the preferred tool.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build Simple Memo and write about capture workflows. Product details are at &lt;a href="https://simplememofast.com/" rel="noopener noreferrer"&gt;simplememofast.com&lt;/a&gt;; the hypothetical scores above are not product benchmarks.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devjournal</category>
      <category>indiehackers</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>MFMailComposeViewController in 2026: five edge cases I hit</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 04 Sep 2026 13:31:01 +0000</pubDate>
      <link>https://dev.to/simple_memo/mfmailcomposeviewcontroller-in-2026-five-edge-cases-i-hit-29pa</link>
      <guid>https://dev.to/simple_memo/mfmailcomposeviewcontroller-in-2026-five-edge-cases-i-hit-29pa</guid>
      <description>&lt;p&gt;Here is the entire mail layer of an app I shipped — one file, under forty lines, no third-party libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;MessageUI&lt;/span&gt;

&lt;span class="c1"&gt;/// A SwiftUI wrapper around Apple's mail compose sheet.&lt;/span&gt;
&lt;span class="c1"&gt;/// Present it ONLY after MFMailComposeViewController.canSendMail() is true.&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;MailComposer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewControllerRepresentable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MFMailComposeResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;@Environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dismiss&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;vc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mailComposeDelegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;
        &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setToRecipients&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSubject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMessageBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;isHTML&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeCoordinator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewControllerDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MailComposer&lt;/span&gt;
        &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MailComposer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;mailComposeController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;didFinishWith&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// tell the app what happened&lt;/span&gt;
            &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;// then close the sheet yourself&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole thing, and it looks boring, which is the point. MessageUI has shipped with iOS since version 3. The surface has barely moved in fifteen years. And yet I burned the better part of two evenings on it the year I shipped this app, because the interesting part of &lt;code&gt;MFMailComposeViewController&lt;/code&gt; is not the code you write. It is the four or five states that code has to survive after you present it.&lt;/p&gt;

&lt;p&gt;In my app a finished note has two sinks. It becomes an email, and, when I have the setting on, it becomes a line appended to a markdown file in my Obsidian vault. This post is only about the first sink. The first sink is where MessageUI lives, and MessageUI is where every surprise was waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the wrapper is actually doing
&lt;/h2&gt;

&lt;p&gt;Three things, and only three.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;makeUIViewController&lt;/code&gt; builds the compose controller, stamps it with the recipient, subject, and body, and points its &lt;code&gt;mailComposeDelegate&lt;/code&gt; at a coordinator. &lt;code&gt;updateUIViewController&lt;/code&gt; does nothing, on purpose: once the sheet is on screen I do not want SwiftUI re-pushing values into it while the user is typing. &lt;code&gt;makeCoordinator&lt;/code&gt; hands back the object that owns the one method that matters, &lt;code&gt;mailComposeController(_:didFinishWith:error:)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@Environment(\.dismiss)&lt;/code&gt; line is the quiet load-bearing piece. I will come back to why in a minute, because forgetting it is edge case number two and it is the one that makes your app look broken in a demo.&lt;/p&gt;

&lt;p&gt;Everything above is textbook. The trouble starts one layer out, at the question of whether you are even allowed to show this sheet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does canSendMail() return false on a phone with Gmail?
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;canSendMail()&lt;/code&gt; does not answer the question you think it answers.&lt;/p&gt;

&lt;p&gt;You read it as "can this person send email." What it actually reports is closer to "is the system Mail composer able to hand a message to a configured account right now." Those are not the same sentence. If someone deleted Apple Mail, or never added an account to it, and lives entirely inside the Gmail or Outlook app, &lt;code&gt;canSendMail()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;. The person can obviously email. Your app just decided they cannot.&lt;/p&gt;

&lt;p&gt;I gated my only send button on this call. The first bug report was from a friend who had never once opened Apple Mail on his phone. To him the app was inert: he tapped the button and nothing happened, because my &lt;code&gt;else&lt;/code&gt; branch did nothing worth doing.&lt;/p&gt;

&lt;p&gt;Here is the matrix I keep taped to the inside of my skull now:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device state&lt;/th&gt;
&lt;th&gt;&lt;code&gt;canSendMail()&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Can the person actually email?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple Mail present, account configured&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple Mail removed, Gmail app installed&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes, just not through MessageUI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No mail app, no account anywhere&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simulator with no account&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No (test on a device)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fix is not to fight &lt;code&gt;canSendMail()&lt;/code&gt;. It is honest and you should trust it. The fix is to have a real answer for the two middle rows: a &lt;code&gt;mailto:&lt;/code&gt; fallback. Since iOS 14 the user can set a third-party app as the default mail handler, so a &lt;code&gt;mailto:&lt;/code&gt; link opens their Gmail or Outlook, not a dead Apple Mail. That single fallback covers the person my &lt;code&gt;if&lt;/code&gt; branch was quietly abandoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Presenting it without stranding anyone
&lt;/h2&gt;

&lt;p&gt;Here is the call site that ties the guard, the sheet, and the fallback together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ComposeButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;noteText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;showingMail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Send note"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canSendMail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;showingMail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;        &lt;span class="c1"&gt;// full composer path&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;openMailtoFallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;// default-mail-app path&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isPresented&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$showingMail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;MailComposer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"me@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="nv"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Note"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;noteText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;markCaptured&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// only .sent&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;openMailtoFallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;noteText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addingPercentEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;withAllowedCharacters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlQueryAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"mailto:me@example.com?subject=Note&amp;amp;body=&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole design decision lives in that &lt;code&gt;if&lt;/code&gt;. Note that I check &lt;code&gt;canSendMail()&lt;/code&gt; inside the button action, not in &lt;code&gt;onAppear&lt;/code&gt; and not stored in a &lt;code&gt;@State&lt;/code&gt; at launch. That is deliberate, and it is the answer to a question I will get to in the FAQ: the value can change while your app is alive. Checking it at the last possible moment, right when the person taps, is the only version that stays correct.&lt;/p&gt;

&lt;p&gt;Note also &lt;code&gt;.urlQueryAllowed&lt;/code&gt; on the fallback. Skip that encoding and a note containing an ampersand, a space, or a &lt;code&gt;#&lt;/code&gt; produces a &lt;code&gt;mailto:&lt;/code&gt; string that either truncates or silently refuses to open. The composer handles all of that for you; the fallback makes you do it by hand, which is a fair summary of the whole trade between the two paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sheet will not close itself
&lt;/h2&gt;

&lt;p&gt;This is the demo-killer.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MFMailComposeViewController&lt;/code&gt; does not dismiss when the user taps Cancel or Send. It fires your delegate and then just sits there, fully presented, waiting for you. If you forget the dismiss call, the sheet freezes on screen after the user thinks they are done, and there is no obvious way out. It reads as a hang.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;mailComposeController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;didFinishWith&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MFMailComposeResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;- the whole reason the coordinator exists&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tutorial screenshot hides this, because a screenshot is the happy path caught once, before anyone taps Cancel. I only found it because I tested Send, saw it work, shipped, and then watched a user tap Cancel in a screen recording and get stuck. The &lt;code&gt;@Environment(\.dismiss)&lt;/code&gt; I flagged earlier is what makes that one line possible from inside the coordinator. Wire it in &lt;code&gt;makeUIViewController&lt;/code&gt;, call it in the delegate, and the sheet behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does .saved actually mean?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;MFMailComposeResult&lt;/code&gt; has four cases, and I had mentally collapsed them into two: it worked, or it did not. That is wrong, and the wrong-ness has a specific cost.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;What the user did&lt;/th&gt;
&lt;th&gt;Did a message leave the device?&lt;/th&gt;
&lt;th&gt;What I show now&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.sent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tapped Send&lt;/td&gt;
&lt;td&gt;Yes, Mail queued it&lt;/td&gt;
&lt;td&gt;"Sent"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.saved&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cancel, then Save Draft&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing, or "Saved a draft"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.cancelled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cancel, then Delete Draft&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send attempt failed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Offer retry, read &lt;code&gt;error&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The trap is &lt;code&gt;.saved&lt;/code&gt;. When the user taps Cancel, iOS asks whether to save a draft. If they say yes, you get &lt;code&gt;.saved&lt;/code&gt;. If your success check is written as "anything that is not &lt;code&gt;.cancelled&lt;/code&gt; counts as sent," you will proudly show a green "Sent" checkmark for a message that is sitting in a drafts folder, unsent, possibly forever. I did exactly that for about a week. The note the person thought they had captured and sent was, from their side, just gone.&lt;/p&gt;

&lt;p&gt;Branch on the specific case. Treat only &lt;code&gt;.sent&lt;/code&gt; as sent. Treat &lt;code&gt;.saved&lt;/code&gt; as its own quiet thing, because it is a real outcome a real person chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simulator quietly can't send
&lt;/h2&gt;

&lt;p&gt;You cannot fully test this flow in the Simulator, and the Simulator will not tell you that in words.&lt;/p&gt;

&lt;p&gt;A fresh simulator has no mail account, so &lt;code&gt;canSendMail()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; and you never see the sheet at all. If you wire around the guard to force the UI up during development, the compose window appears, you tap Send, the sheet dismisses, and nothing is delivered, because there is no account behind it. The failure is silent. There is no error dialog, no red console line. It just does not happen.&lt;/p&gt;

&lt;p&gt;The only reliable way I found to exercise all four &lt;code&gt;MFMailComposeResult&lt;/code&gt; branches is a physical device with a real account, tapping Send, Cancel-and-save, and Cancel-and-delete by hand and watching what my delegate does. Budget the ten minutes on hardware. I did not, at first, and I paid for it with the &lt;code&gt;.saved&lt;/code&gt; bug above, which the Simulator structurally could not have shown me.&lt;/p&gt;

&lt;h2&gt;
  
  
  error is not the signal; result is
&lt;/h2&gt;

&lt;p&gt;The delegate hands you an &lt;code&gt;Error?&lt;/code&gt;, and it is a decoy.&lt;/p&gt;

&lt;p&gt;In practice that &lt;code&gt;error&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt; almost every time, including on outcomes you would loosely call failures. A cancel is not an error. A saved draft is not an error. &lt;code&gt;.failed&lt;/code&gt; is where an error can appear, and even there it is often thin. If you write your logic as &lt;code&gt;if error == nil { showSuccess() }&lt;/code&gt;, you will show success for cancels and saved drafts alike, because their error is &lt;code&gt;nil&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;The signal is &lt;code&gt;result&lt;/code&gt;. Read the enum, switch on all four cases, and reach for &lt;code&gt;error&lt;/code&gt; only inside &lt;code&gt;.failed&lt;/code&gt;, and only to decide what to tell the user or whether a retry is worth attempting. I inverted this at first because every other iOS callback trains you to check the error object first. This one is the exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fallback has its own edges
&lt;/h2&gt;

&lt;p&gt;Once you accept that &lt;code&gt;canSendMail()&lt;/code&gt; will be &lt;code&gt;false&lt;/code&gt; for a chunk of real users, the &lt;code&gt;mailto:&lt;/code&gt; fallback stops being a nicety and becomes half the feature. It has limits worth knowing before you lean on it.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;mailto:&lt;/code&gt; URL carries the body as a percent-encoded query string. Long notes bump into practical length ceilings, the encoding has to be exact or the link silently fails to open, and there are no attachments — &lt;code&gt;mailto:&lt;/code&gt; cannot carry a file. So the fallback is not a drop-in twin of the composer. It is a narrower path for the plain-text case, which, for a note-to-email app, happens to be almost every case.&lt;/p&gt;

&lt;p&gt;And when the composer itself returns &lt;code&gt;.failed&lt;/code&gt;, the message has to land somewhere that will try again later rather than vanish. That queue is a separate piece of machinery with its own retry and ordering rules; I took it apart in &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;an earlier write-up on the offline-first outbox&lt;/a&gt;. The mail sheet is only the front door. What happens after &lt;code&gt;.failed&lt;/code&gt; is where reliability actually lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change after shipping it
&lt;/h2&gt;

&lt;p&gt;Two things, now that the dust settled.&lt;/p&gt;

&lt;p&gt;First, I would stop treating &lt;code&gt;canSendMail()&lt;/code&gt; as a feature flag and treat &lt;code&gt;mailto:&lt;/code&gt; as the default door, with the full composer as the enhancement layered on top when it is available. I built it the other way around, composer-first, and spent the fallback as an afterthought. The afterthought turned out to serve the users I most wanted to keep.&lt;/p&gt;

&lt;p&gt;Second, I would log the four &lt;code&gt;MFMailComposeResult&lt;/code&gt; cases from day one. I have no idea how often real people hit &lt;code&gt;.saved&lt;/code&gt; versus &lt;code&gt;.sent&lt;/code&gt;, and I wish I did, because it would tell me whether "Save Draft" is a genuine intent I should support better or an accident I should design away. I added that logging late. Instrument the boring enum early; it is the only honest record of what people actually do with your send button.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does &lt;code&gt;canSendMail()&lt;/code&gt; change while my app is running?&lt;/strong&gt;&lt;br&gt;
It can. A person can add or remove a mail account in Settings while your app is backgrounded. Call &lt;code&gt;canSendMail()&lt;/code&gt; right before you present the sheet, not once at launch and never again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this wrapper work on iPad?&lt;/strong&gt;&lt;br&gt;
Yes. &lt;code&gt;MFMailComposeViewController&lt;/code&gt; presents as a form sheet on iPad without extra work. The delegate, the four results, and the dismiss requirement are all identical; nothing about the edge cases above is iPhone-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I just skip the composer and use a &lt;code&gt;mailto:&lt;/code&gt; link everywhere?&lt;/strong&gt;&lt;br&gt;
Only if you never need attachments, rich control over the message, or the in-app compose UI people already trust. &lt;code&gt;mailto:&lt;/code&gt; is universal and reaches third-party default mail apps, but it is plain-text, length-limited, and attachment-free. I use the composer when &lt;code&gt;canSendMail()&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;mailto:&lt;/code&gt; as the fallback, so each covers the other's blind spot.&lt;/p&gt;

&lt;p&gt;If you ship anything that sends mail: do you gate on &lt;code&gt;canSendMail()&lt;/code&gt;, or do you always offer a &lt;code&gt;mailto:&lt;/code&gt; fallback? I want to hear the split, and the one bug that made you pick.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build Simple Memo alone — &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;an iOS app&lt;/a&gt; that turns the note you just typed into an email in about 0.3 seconds. I post here when the boring parts of shipping teach me something.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>iosdev</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What gets a one-person blog cited by an LLM, and what doesn't</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 01 Sep 2026 13:28:17 +0000</pubDate>
      <link>https://dev.to/simple_memo/what-gets-a-one-person-blog-cited-by-an-llm-and-what-doesnt-1bkp</link>
      <guid>https://dev.to/simple_memo/what-gets-a-one-person-blog-cited-by-an-llm-and-what-doesnt-1bkp</guid>
      <description>&lt;p&gt;&lt;strong&gt;Q1: When does an AI actually quote a blog like mine, instead of crawling past it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When one sentence can stand completely on its own. I run a one-person site of a few dozen posts, and of the handful an AI Overview or an assistant has ever quoted back to me, not one was the post I'd have bet on. They weren't the long ones. They weren't even the ones that ranked. In every case the thing that got lifted was a single self-contained line stating one specific fact, whether a number, a constraint, or a definition, that didn't need the paragraph around it to make sense. The machine didn't take my page. It took a sentence and left the rest on the floor.&lt;/p&gt;

&lt;p&gt;That reframed the whole exercise for me. For years I wrote to be found. But the question that decides whether a model cites you is narrower than that: can it pull one true, attributable claim out of your page without dragging along context it has no way to verify. Most of my writing failed that test without my ever noticing, because a human reader forgave what a machine will not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2: Isn't this just SEO with a new coat of paint?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No, and the gap between the two is the whole point. Ranking optimizes for position: where your link sits among ten others, competing on authority and backlinks and freshness. Citation optimizes for extractability: whether one claim on your page can be lifted and attributed to you. You can rank tenth and still be the line an AI Overview quotes, and you can rank first and get skimmed past because a competitor wrote the same fact more cleanly.&lt;/p&gt;

&lt;p&gt;Here is the split, the way I ended up drawing it for myself:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Optimizing to rank&lt;/th&gt;
&lt;th&gt;Optimizing to be quoted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The target&lt;/td&gt;
&lt;td&gt;A position on the results page&lt;/td&gt;
&lt;td&gt;One liftable, attributable claim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The unit&lt;/td&gt;
&lt;td&gt;The page&lt;/td&gt;
&lt;td&gt;The sentence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What wins&lt;/td&gt;
&lt;td&gt;Authority, backlinks, freshness&lt;/td&gt;
&lt;td&gt;Specificity, self-containment, being the origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How you measure&lt;/td&gt;
&lt;td&gt;Traffic and position&lt;/td&gt;
&lt;td&gt;Whether your line shows up inside the answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The failure mode&lt;/td&gt;
&lt;td&gt;Buried on page two&lt;/td&gt;
&lt;td&gt;Crawled, indexed, never the quote&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two are not enemies. A page still has to be crawlable and trustworthy before any of this matters, so the old work isn't wasted. But once you are in the index, the lever that gets you into the generated answer is not more of the ranking game. It is writing claims a machine can safely repeat with your name on them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3: So what specifically made a post of mine get quoted?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One claim per unit, with the concrete detail still attached to it. The posts that got picked up all shared a shape: each section made exactly one point, and the point was phrased as a sentence you could screenshot on its own. Not "performance matters, and here are the reasons," but "a note reaches my email about a second after I type it, because the send fires before the interface finishes animating." That second sentence carries its own subject, its own number, and its own mechanism. An assistant can hand it to a stranger and it survives the trip intact.&lt;/p&gt;

&lt;p&gt;The other thing they shared: I was the origin. When you are the person who built the thing, measured the thing, or made the call, your sentence is a primary source rather than a summary of five other blogs. These systems lean visibly toward the page that looks like where a fact started. For a one-person blog that is the single structural edge you hold over a content farm. You actually did the work, so the move is to write the one sentence only you could have written, and to put the specific number in it that nobody downstream can reproduce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4: What backfired?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The shortcuts, almost all of them. I tried the tactics the "optimize for AI" posts push, and most either did nothing or actively made the writing worse. Bolting a FAQ block onto every post so it would look answer-shaped got ignored, because the answers were generic and a model already holds ten cleaner copies of the same non-answer. Front-loading a keyword-dense summary produced prose that read like a robot writing for another robot, which I suspect is precisely the texture these systems are being tuned to discount. Treating schema markup as a magic ingredient helped a parser see my structure but did nothing for a vague claim. There is no markup attribute for "says something specific and true."&lt;/p&gt;

&lt;p&gt;The common thread in the failures is that each one tried to fake being a source instead of being one. You cannot keyword your way into being the place a fact originated. The only thing that reliably worked was the unglamorous version: know something specific, then say it plainly enough to lift. Everything I did that was aimed at the machine rather than at the truth of the sentence came back empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5: Does the structure matter, or just the words?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both, but structure only earns its keep when it serves extraction. Question-shaped headings help, because the heading becomes the query and the paragraph beneath it becomes the answer — which is, not by accident, why this whole post is a list of questions. One idea per paragraph helps, because a model handed three interleaved claims will usually just skip the knot rather than untangle it. A short definition placed early helps, because assistants love to quote the one-line "X is …" form and attribute it cleanly. A comparison table helps, because its rows are already atomic units.&lt;/p&gt;

&lt;p&gt;None of that is novel advice for human readers. Clear structure was always just good writing. What changed is that a second reader now enforces it mechanically. Sloppy structure used to cost you a confused person who might still puzzle it out and stick around; now it costs you a machine that feels nothing and simply moves to a tidier source. The penalty for disorganization got automated, and it got stricter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6: How do you actually write a sentence a machine will lift?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Write it so it survives being torn off the page. In practice that means no orphaned pronouns: a sentence that opens "This is why it fails" is dead on arrival, because the model can't carry "this" or "it" out of context with it. Name the subject. State the claim. Keep the specific detail inside the same sentence instead of two paragraphs down where it gets stranded. I've argued before that &lt;a href="https://dev.to/simple_memo/the-prompt-is-the-cheap-part-the-context-is-the-product-dh0"&gt;in the LLM era the context is the product&lt;/a&gt;, and this is that same idea aimed at a single line: the sentence has to bring its own context along.&lt;/p&gt;

&lt;p&gt;The test I use is one question. Could I paste this one line into a chat with a stranger, with zero setup, and would it still be true and legible? If yes, an assistant can do exactly that on my behalf. If it needs the paragraph above it to mean anything, I either rewrite it or I accept it will never be the quote. It turns out to be the same discipline I use when I write &lt;a href="https://dev.to/simple_memo/how-i-chose-a-note-format-future-me-and-an-llm-can-both-read-46d9"&gt;notes a model will read back to me later&lt;/a&gt;: self-contained, dated, free of anaphora. I just moved it to the public side of the desk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7: Does this work the same across AI Overviews, Perplexity, and ChatGPT?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not identically, but the thing they reward is the same. Google's AI Overviews tend to synthesize a short answer and sometimes surface a link or two beneath it, so being the cleanest phrasing of a fact gives you a shot at both the summary and the click. Perplexity is the most explicit about it, stapling numbered citations straight onto the sentences it borrows, which makes it the clearest place to actually watch which of your lines got used. An assistant like ChatGPT with browsing will pull a claim into its answer and may or may not name you, depending on how the person asked.&lt;/p&gt;

&lt;p&gt;The mechanics differ, and they keep changing, so I stopped trying to optimize per product. Underneath all of them sits one reader that reads straight through and wants a self-contained, attributable claim. Write for that reader and you are roughly right everywhere at once. Chase one product's current quirk and you are rewriting the day it ships an update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8: Is chasing LLM citations even worth it for a one-person blog?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Honestly, the direct payoff is small and a little strange. The traffic from being quoted is a trickle, it is genuinely hard to attribute, and some assistants quote you with no link at all, so you may never learn it happened. If you are chasing a measurable pipeline this quarter, a one-person blog is the wrong instrument and this is the wrong lever.&lt;/p&gt;

&lt;p&gt;What makes it worth doing anyway is that being cited compounds in a way ranking does not. Once you are the sentence a model reaches for on some narrow question, you tend to stay it, because you were the origin and the origin doesn't drift. And the entire cost of getting there is a habit I wanted for its own sake: say one specific, true thing per post, and phrase it so plainly a stranger could carry it off. That produces better reading for the humans too. I would keep doing it if every model went dark tomorrow, and that is the only reason I trust it is a discipline and not a fad I talked myself into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q9: The one I'll hand back to you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If an AI has ever quoted something you wrote, go find the exact line it lifted and look hard at it. Tell me what that sentence had in common with the rest of your writing, or how it stood apart from it. I have a guess about the shape of the ones that get picked, but my whole sample is a dozen posts on one small site, and yours is the part I can't see from here.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo dev. I build Simple Memo, an iOS app that drops a line of text into my email about a second after I type it, and I keep &lt;a href="https://simplememofast.com/" rel="noopener noreferrer"&gt;one small site&lt;/a&gt; around it where I try ideas like this in public.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>llm</category>
      <category>seo</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Every feature I add, I maintain alone forever. So I stopped.</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 28 Aug 2026 13:25:15 +0000</pubDate>
      <link>https://dev.to/simple_memo/every-feature-i-add-i-maintain-alone-forever-so-i-stopped-5n9</link>
      <guid>https://dev.to/simple_memo/every-feature-i-add-i-maintain-alone-forever-so-i-stopped-5n9</guid>
      <description>&lt;p&gt;The advice every solo developer eventually hears is to ship more features to stay competitive. After about fifteen months and eleven releases of a one-person iOS app, I have come to think that advice is quietly dangerous for people like me, and I have started doing the opposite. I now say no to most of the feature requests I get, including the ones that come from me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For a team, the real cost of a feature is spread across several people and years; for a solo dev, the person who builds it is also the one who supports it, re-tests it after every OS beta, and explains it forever.&lt;/li&gt;
&lt;li&gt;So I made "no" the default. A feature has to earn a yes against its standing cost, not its build cost, and I keep a written list of what I refused where other people keep a roadmap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The advice, stated as fairly as I can
&lt;/h2&gt;

&lt;p&gt;Let me argue the other side first, because it is not stupid.&lt;/p&gt;

&lt;p&gt;Features are how most software earns its keep. Users leave products that feel frozen. A visible roadmap signals momentum to the people deciding whether to trust you. When a competitor ships the thing your users keep asking for, "we chose to stay small" reads as a press release for your own decline. And there is a real craft satisfaction in building: shipping a feature feels like progress in a way that fixing the same three bugs does not.&lt;/p&gt;

&lt;p&gt;All of that is true. It is also advice that was minted inside teams, and mostly inside teams with money. When a five-person squad ships a feature, the person who wrote it is not the only one holding it afterward. There is someone on support who fields the confused emails, someone on QA who re-runs the regression, someone on call at 2am, and a backlog owner who decides when it gets revisited. The build cost is loud and shared. The ownership cost is quiet and distributed. Nobody in that room feels the full weight of the thing they just shipped, which is exactly why they can keep shipping.&lt;/p&gt;

&lt;p&gt;I do not have that room. I hit the same mismatch the first time I tried to run my work through a proper issue tracker, which I wrote about in &lt;a href="https://dev.to/simple_memo/the-issue-tracker-was-built-for-a-team-i-dont-have-4pli"&gt;the issue tracker was built for a team I don't have&lt;/a&gt;. The tool assumed colleagues I did not have. Feature-first advice makes the same assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is a feature so much more expensive for one person?
&lt;/h2&gt;

&lt;p&gt;Because I am every seat in that room at once.&lt;/p&gt;

&lt;p&gt;When I ship a feature, I am also the support desk that will answer for it, the tester who re-checks it against the next iOS beta, the designer who now has one more screen to keep coherent, and the future maintainer who will open that file in eight months having forgotten how it works. The day I ship a feature is the cheapest day that feature will ever have. Every day after is standing cost.&lt;/p&gt;

&lt;p&gt;Here is the split I wish someone had drawn for me before I started saying yes to things:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A feature's cost&lt;/th&gt;
&lt;th&gt;Build (one-time)&lt;/th&gt;
&lt;th&gt;Standing (recurring, and all mine)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;A few days of work&lt;/td&gt;
&lt;td&gt;Re-read and re-tested every OS beta, forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support&lt;/td&gt;
&lt;td&gt;None yet&lt;/td&gt;
&lt;td&gt;Every edge case becomes an email in my inbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;One new screen or toggle&lt;/td&gt;
&lt;td&gt;One more thing to explain, and to keep consistent with the rest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attention&lt;/td&gt;
&lt;td&gt;Fun while it is fresh&lt;/td&gt;
&lt;td&gt;Competes with every other feature I already own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review risk&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;One more surface Apple can reject an update over&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The left column is what people estimate. The right column is what actually runs your life as a solo dev. I have shipped features that took two days to build and then quietly taxed me for a year. I have refused features that would have taken two days to build and saved myself that same year without ever noticing, because you do not get a notification for the support thread you never had.&lt;/p&gt;

&lt;h2&gt;
  
  
  The refusals I'm glad I made
&lt;/h2&gt;

&lt;p&gt;Concretely, here is what I have said no to, and what saying no bought me.&lt;/p&gt;

&lt;p&gt;Folders and tags. The single most requested organizing feature, and the one I am most relieved I skipped. Every note app that adds them inherits a second, permanent job: teaching people a filing system, then supporting the people who file wrong. My app has none. The whole model is one stream, newest first. That refusal is why my settings screen still fits on one page with six toggles on it, and why I have never once answered an email about a note someone "lost" in a folder.&lt;/p&gt;

&lt;p&gt;Cross-device account sync. Real demand, real value, and a genuinely hard thing to own alone: accounts, a server, conflict resolution, a privacy surface, and a support burden that never sleeps. Saying no kept me out of a category of 2am problem I am not staffed to have. People who need that reach for it elsewhere, and I am at peace with losing them.&lt;/p&gt;

&lt;p&gt;Rich text, themes, a home-screen widget with live counts. Each is a small yes that becomes a permanent line item on the regression list. I turned all three down. The app does one thing, and the number of ways it can break is bounded by how few things it does.&lt;/p&gt;

&lt;p&gt;The pattern under all of it: the features I refused are invisible in the product and enormous in my calendar. Nobody writes a review thanking you for the complexity you spared them.&lt;/p&gt;

&lt;h2&gt;
  
  
  When "no" is just fear wearing a strategy
&lt;/h2&gt;

&lt;p&gt;I have to be honest here, because "I keep it simple on purpose" is the most flattering possible cover story for "I was scared of the hard thing."&lt;/p&gt;

&lt;p&gt;Some of my refusals were not discipline. They were avoidance. There was a feature several users needed that touched a part of iOS I found intimidating, and for two releases I told myself it was "out of scope." It was not out of scope. It was inside my discomfort. When I finally built it, it was fine, and I had made real people wait on my nerves. Minimalism can be a philosophy or an alibi, and from the inside they feel identical.&lt;/p&gt;

&lt;p&gt;The other failure mode is subtler: refusing things until the product quietly stops being alive. I have watched myself do a gentler version of that, and I wrote about the time I removed too much in &lt;a href="https://dev.to/simple_memo/i-killed-every-meeting-as-a-solo-dev-half-of-it-backfired-58ei"&gt;I killed every meeting as a solo dev, and half of it backfired&lt;/a&gt;. Subtraction is a tool, not a personality. A codebase can die of too many features. It can also die of an owner who has fallen in love with saying no.&lt;/p&gt;

&lt;p&gt;So the goal is not zero features. It is refusing by default while staying honest about which "no" is strategy and which is flinching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test I run before saying yes now
&lt;/h2&gt;

&lt;p&gt;I stopped judging features by whether they would be nice to have. Everything is nice to have. I judge them by their standing cost, with one question:&lt;/p&gt;

&lt;p&gt;Would I still want this if I had to maintain it, alone, through the next three iOS versions, and it brought in no new users at all?&lt;/p&gt;

&lt;p&gt;That framing strips out the fun of building and the fantasy of the users it might attract, and leaves only the part I will actually live with. Most requests do not survive it, including most of mine. The few that do tend to be the ones that make the core thing better rather than add a new thing beside it. Those are worth the standing cost. I have shipped a handful of features that passed this test, and they are the only ones I have never regretted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually do instead of a roadmap
&lt;/h2&gt;

&lt;p&gt;I keep a "no" list.&lt;/p&gt;

&lt;p&gt;It is a plain file of features I have declined, each with one line about why. It does two things a roadmap cannot. It lets me say no quickly and consistently, because the reasoning is already written down and I am not relitigating it every time someone asks. And it is honest in a way a roadmap is not: a roadmap is a list of debts you intend to take on, while a "no" list is a record of the ones you chose not to. When I get the same request three times, I move it from the no list to a much shorter "maybe, and here is the standing cost" list, and it has to pass the test above to graduate from there.&lt;/p&gt;

&lt;p&gt;The result is an app that does less than almost everything it competes with, shipped by one person who is still standing after fifteen months. I think those two facts are the same fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I get about this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Doesn't this just mean your app stays small forever?&lt;/strong&gt;&lt;br&gt;
Probably, yes. Small is not the failure mode I am worried about. The failure mode I am worried about is an app so wide that one person can no longer hold all of it in their head, which is the moment solo maintenance quietly becomes impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you tell users no without losing them?&lt;/strong&gt;&lt;br&gt;
Sometimes I do lose them, and I have made peace with that. But most people are fine with "that is not what this app is for" if you say it plainly and fast. What they hate is silence, or a "great idea, on the roadmap!" that never arrives. A clear no ages better than a soft maybe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't "do one thing well" just an excuse to do less work?&lt;/strong&gt;&lt;br&gt;
It can be, and I said above where it was exactly that for me. The difference is whether the thing you kept is actually done well. Refusal only earns its keep if the narrow core is genuinely good. Otherwise you have not built a focused product, you have built an unfinished one.&lt;/p&gt;

&lt;p&gt;If you ship alone: what is the one feature you are most glad you never built, and what do you think saying no to it actually saved you, in support or in sanity? I am collecting these, because the honest ones are more useful than any roadmap.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo developer who has spent about fifteen months building &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;a deliberately small iOS capture app&lt;/a&gt;. I post here every few days about the unglamorous parts of shipping alone, this one included.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>indiehackers</category>
      <category>buildinpublic</category>
      <category>programming</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>I counted 1,890 daily-note lines. My memory was wrong.</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:27:16 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-counted-1890-daily-note-lines-my-memory-was-wrong-5ghg</link>
      <guid>https://dev.to/simple_memo/i-counted-1890-daily-note-lines-my-memory-was-wrong-5ghg</guid>
      <description>&lt;p&gt;For six months I kept every fleeting thought in one file: a single daily note, one line each, &lt;code&gt;- HH:mm&lt;/code&gt; then a verb then the thought. In July I exported the whole thing and counted. 1,890 lines across 181 days. Almost nothing I &lt;em&gt;believed&lt;/em&gt; about my own days survived the count.&lt;/p&gt;

&lt;p&gt;The short version: my mornings are not for thinking, my evenings quietly do most of the real work, and 94% of what I wrote down never became anything. The file I'd been treating as a weak knowledge base turned out to be an excellent buffer, graded on the wrong exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why count your own notes at all?
&lt;/h2&gt;

&lt;p&gt;I wasn't trying to optimize anything. I'd just noticed I kept &lt;em&gt;saying&lt;/em&gt; things about how I work ("I think best in the morning," "I mostly track tasks") that I had no evidence for. And there was the evidence, six months of it, timestamped, sitting in a file I opened every day.&lt;/p&gt;

&lt;p&gt;Counting took an afternoon. I split each line on its leading timestamp, bucketed by hour, and grouped by the first verb. No dashboard, no app, one throwaway script and a spreadsheet. The method only worked because the format made it work: every line already carried a time and a verb, so the structure I needed was a side effect of how I'd been capturing all along. Had the lines been paragraphs in a journal, none of it would have been countable. Consistency at capture time is what buys you countability later.&lt;/p&gt;

&lt;p&gt;Here is what I found, next to what I would have told you before I looked:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I believed&lt;/th&gt;
&lt;th&gt;What 1,890 lines showed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;My best thinking happens in the morning&lt;/td&gt;
&lt;td&gt;41% of my idea and note lines landed after 21:00; only 9% of my todos and calls did&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I capture evenly across the day&lt;/td&gt;
&lt;td&gt;one two-hour window (21:00–22:59) held 430 lines, 23% of everything; the six hours after midnight held 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The log is my knowledge base&lt;/td&gt;
&lt;td&gt;94% of lines never became anything else; 6% graduated to their own file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I mostly write down tasks&lt;/td&gt;
&lt;td&gt;the biggest bucket was notes (442 lines, 23%), not todos (285, 15%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A packed day means more lines&lt;/td&gt;
&lt;td&gt;mean 11.3 lines a day, median 9; one shipping day alone held 39&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What did the timestamps actually say?
&lt;/h2&gt;

&lt;p&gt;The day has a shape, and it wasn't the one I pictured. Two hours, 21:00 to 22:59, held 430 of the 1,890 lines. Nearly a quarter of six months of thinking happened in a window I'd have described as winding down. The single busiest hour was 21:00 with 224 lines; 22:00 came second at 206.&lt;/p&gt;

&lt;p&gt;Mornings were busy too, 454 lines between 07:00 and noon, but busy with a different kind of line. The 07:00–09:00 block ran on todo, call, email, reply. Logistics. The hours after midnight were almost empty: 22 lines total from midnight to 06:00, most of them the one or two nights I couldn't sleep.&lt;/p&gt;

&lt;p&gt;Split by verb, the gap sharpened into something I couldn't wave away. Of my idea and note lines (the 620 where I was thinking rather than administering), 41% landed after 21:00. Of my todo and call lines, 9% did. Same file, same person, and I had the two halves of the clock backwards in my head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the thinking actually happen, then?
&lt;/h2&gt;

&lt;p&gt;I'd been guarding my mornings for deep work and spending the evening on scraps. The count says I had it inverted. My mornings clear the queue; the thinking I claim to value shows up late, compressed, after the day's obligations are spent. Once I saw it I could feel it was true. The 21:00 lines are the ones with questions in them, half-formed, the ones actually worth keeping.&lt;/p&gt;

&lt;p&gt;The averages misled me in a quieter way. A packed day, I'd assumed, produced more lines — loosely true and mostly useless. The mean was 11.3 lines a logged day; the median was 9. A handful of incident days, one shipping day at 39 lines, drag the average up past any ordinary day. If I'd only ever looked at the mean, I'd have pictured a life that basically never happened. The median is the honest number, and it's the one I'd never bothered to compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 94% nobody warns you about
&lt;/h2&gt;

&lt;p&gt;Here is the finding that reorganized how I think about the whole setup. Of 1,890 lines, 113 (6%) ever became something else: promoted to their own file in the vault, turned into a task I actually finished, folded into a piece of writing. The other 1,777 sat there dated and untouched forever.&lt;/p&gt;

&lt;p&gt;My first reaction was that 94% is a failure rate. Then I sat with it. Those lines were never meant to graduate. They were meant to get out of my head so I could keep moving, and they did that the instant I typed them. Capture was the entire job. I'd been judging a buffer as if it were a library. This is the flip side of something I argued a couple of weeks back, that &lt;a href="https://dev.to/simple_memo/the-worst-time-to-organize-a-note-is-the-moment-you-take-it-1fi9"&gt;the worst moment to organize a note is the moment you take it&lt;/a&gt;: if organizing can always wait, it turns out that most of the time it waits forever, and the line is no worse for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number I don't trust
&lt;/h2&gt;

&lt;p&gt;One caveat I have to say plainly, because the tidy 41% flatters me. I only counted what I bothered to write. The thoughts that were too fast, too dull, or too half-formed to log are invisible to the spreadsheet, so "41% of my ideas arrive after 21:00" honestly means "41% of my &lt;em&gt;logged&lt;/em&gt; ideas." It's entirely possible my mornings are full of thinking I never capture because I'm heads-down doing it. The count corrects my memory, but it can't see its own blind spot. I trust the shape of the day more than any single percentage in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed after I saw the numbers?
&lt;/h2&gt;

&lt;p&gt;Not much, and all of it small. I stopped scheduling anything that needs a clear head before 10:00; that slot goes to the queue on purpose now, and I feel less guilty about it. I started protecting the 21:00–22:30 window the way I used to protect mornings: no chores, no inbox, just the file open. And I quit apologizing for a messy midday, because the messy midday is exactly where logistics are supposed to live.&lt;/p&gt;

&lt;p&gt;The count answered one question and handed me a sharper one. I know &lt;em&gt;when&lt;/em&gt; a line gets written. I have no idea how long it then sits before I act on it — whether the 21:00 idea gets touched the next morning or rots for three weeks. So I've begun stamping a second time on the lines I finish, a small &lt;code&gt;done HH:mm&lt;/code&gt; at the end, and in a few months I'll have a distribution for time-to-action instead of time-to-capture. My guess is it will embarrass me again. That's the whole point of measuring the thing instead of remembering it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How long did the counting actually take?&lt;/strong&gt;&lt;br&gt;
About an afternoon, most of it spent fixing lines that didn't match my own format. The counting was a dozen lines of script; the mess was six months of me being inconsistent with myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a special app for this?&lt;/strong&gt;&lt;br&gt;
No. Any log where each line starts with a timestamp will do, and plain text is plenty. The only real requirement is that you were consistent enough that a machine can split the lines cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't 1,890 lines too small to conclude anything?&lt;/strong&gt;&lt;br&gt;
For strong claims, yes. I'm not publishing a study, I'm correcting my own memory, and the bar for that is just "better than a vibe." A number I can re-check beats a story I told myself, even a small number.&lt;/p&gt;

&lt;p&gt;If you keep any kind of timestamped log, run one query before you reply: which hour holds the most of your lines, and does it match the hour you'd have &lt;em&gt;said&lt;/em&gt; you do your best work? I'm collecting the mismatches. Mine was three hours off.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo dev who keeps one dated line per thought; on the days I curate, those lines flow into &lt;a href="https://simplememofast.com/obsidian/" rel="noopener noreferrer"&gt;a plain markdown vault&lt;/a&gt;. I write here every few days about the parts of working alone I only believe once I've counted them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>discuss</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>The cipher was the easy part: AES-GCM in a personal app</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 21 Aug 2026 13:31:28 +0000</pubDate>
      <link>https://dev.to/simple_memo/the-cipher-was-the-easy-part-aes-gcm-in-a-personal-app-75c</link>
      <guid>https://dev.to/simple_memo/the-cipher-was-the-easy-part-aes-gcm-in-a-personal-app-75c</guid>
      <description>&lt;p&gt;Encrypting the notes in my iOS app came down to three lines of CryptoKit. Those lines took about ten minutes to write. Everything they quietly stand on took the next four months to get right. So this is me disassembling the whole path, top to bottom: from a line you typed to the place the key sleeps at night. Every failure that cost me a real weekend lived in a layer the tutorials finish in one sentence.&lt;/p&gt;

&lt;p&gt;The reframe I want to sell you is this: the cipher is the commodity part. AES-GCM is a solved, boring, well-audited primitive, and Apple hands it to you behind a friendly API. The hard part is everything around it, which is key management, and key management is not cryptography. It is bookkeeping, lifecycle, and a set of honest decisions about what you are actually defending against. I got all of those wrong at least once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the teardown found:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AES-GCM call is a few lines and you will not spend meaningful time there. Budget your attention for the four layers underneath it.&lt;/li&gt;
&lt;li&gt;The nonce is a trap only if you try to be clever. Let CryptoKit generate it, store it inline, and never invent your own counter.&lt;/li&gt;
&lt;li&gt;Where the key lives decides whether your data survives a device restore. I learned this the expensive way, from a user who could not read their own notes on a new phone.&lt;/li&gt;
&lt;li&gt;Put a version byte in front of every blob on day one, or you will regret it the first time you have to change anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The cipher everyone shows you
&lt;/h2&gt;

&lt;p&gt;Here is the whole cipher, the part a tutorial spends its entire length on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;CryptoKit&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SymmetricKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bits256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sealed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GCM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sealed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;combined&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;            &lt;span class="c1"&gt;// nonce + ciphertext + tag, ready to store&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read it back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GCM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;SealedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GCM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. &lt;code&gt;seal&lt;/code&gt; generates a fresh random nonce for you, encrypts, and computes a 128-bit authentication tag that refuses to decrypt if a single byte was flipped. The &lt;code&gt;combined&lt;/code&gt; property packs the 12-byte nonce, the ciphertext, and the 16-byte tag into one &lt;code&gt;Data&lt;/code&gt; you can write to disk. If those five lines were the job, this post would be over. They are maybe two percent of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer one: the nonce nobody warned you about
&lt;/h2&gt;

&lt;p&gt;Galois/Counter Mode has one catastrophic failure mode, and it is not a weak cipher. It is a repeated nonce. Encrypt two different messages under the same key and the same nonce and you do not leak a little. You leak the XOR of the two plaintexts, and worse, you hand an attacker the pieces to forge messages that will pass the authentication check. This is the single scariest sentence in the whole subject, and it is exactly why a tutorial that skips it is doing you harm.&lt;/p&gt;

&lt;p&gt;Here is the relief: if you do what I showed above and let CryptoKit pick the nonce, you are almost certainly fine. It draws 12 random bytes from a cryptographically secure source every time, and the collision odds are astronomically small for any personal-scale data set. The trap opens the moment you decide to be helpful. Early on I tried, at various points, to store the nonce in its own column to "save space," to rederive it from a message counter, and to reuse one nonce across a batch so decryption would be faster. Every one of those instincts is a way to walk yourself into the failure mode on purpose. The correct move is the lazy one: generate, store inline with &lt;code&gt;combined&lt;/code&gt;, never reuse, never reconstruct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer two: where does the key actually live?
&lt;/h2&gt;

&lt;p&gt;Now the real question, the one the cipher API cannot answer for you: where do you keep the key? A 256-bit &lt;code&gt;SymmetricKey&lt;/code&gt; is just bytes. If an attacker can read those bytes, the encryption is decoration. So every option below is really a claim about who can reach the key, and about what happens to it when the phone changes hands or gets restored from a backup.&lt;/p&gt;

&lt;p&gt;I have shipped or seriously prototyped all five of these. Here is the honest comparison, including the part that bit me.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where the key lives&lt;/th&gt;
&lt;th&gt;Survives a restore to a new device?&lt;/th&gt;
&lt;th&gt;What it actually protects against&lt;/th&gt;
&lt;th&gt;The catch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardcoded constant in the app&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Almost nothing; it ships in every copy of the binary&lt;/td&gt;
&lt;td&gt;Anyone who unzips your app has the key. This is theater.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derived from a user passphrase&lt;/td&gt;
&lt;td&gt;Yes, if the user remembers it&lt;/td&gt;
&lt;td&gt;A stolen device with no passphrase entered&lt;/td&gt;
&lt;td&gt;You now own a password-reset problem and a KDF (use a deliberately slow one)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keychain, &lt;code&gt;ThisDeviceOnly&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Other apps, and off-device attackers&lt;/td&gt;
&lt;td&gt;Restore to a new phone and the key is gone. If the ciphertext synced, the data is now unreadable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keychain, &lt;code&gt;AfterFirstUnlock&lt;/code&gt; (syncable)&lt;/td&gt;
&lt;td&gt;Yes, via encrypted Keychain backup&lt;/td&gt;
&lt;td&gt;Other apps and casual access&lt;/td&gt;
&lt;td&gt;Wider exposure window; the key rides along in iCloud Keychain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secure Enclave, wrapping the key&lt;/td&gt;
&lt;td&gt;No (the Enclave key is device-bound)&lt;/td&gt;
&lt;td&gt;Extraction even off a jailbroken device&lt;/td&gt;
&lt;td&gt;The Enclave cannot hold an AES key at all; it holds a P-256 key that wraps yours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the one people get wrong in conversation, so it is worth stating plainly. You cannot put a symmetric AES-256 key inside the Secure Enclave. The Enclave only stores P-256 elliptic-curve keys, and it only performs signing and key agreement. The real pattern is indirect: you generate a P-256 key that never leaves the Enclave, use key agreement plus HKDF to derive or unwrap your symmetric key on demand, and keep only the wrapped blob on disk. It is a good pattern. It is also several more moving parts than "call seal," which is the whole theme of this teardown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer three: the restore that hands back unreadable data
&lt;/h2&gt;

&lt;p&gt;This is the layer that cost me a weekend and one apologetic email. Early on I did the reasonable-sounding thing and stored the key in the Keychain with &lt;code&gt;kSecAttrAccessibleWhenUnlockedThisDeviceOnly&lt;/code&gt;, because "this device only" sounds like the safe, private choice. It is safe, for the key. The problem is what happens next. If any of the encrypted notes are backed up or synced off the device, and the user then restores onto a new phone, the ciphertext comes back and the key does not. &lt;code&gt;ThisDeviceOnly&lt;/code&gt; items are deliberately excluded from backups. The user is now holding a pile of their own notes that nothing on Earth can decrypt, me included.&lt;/p&gt;

&lt;p&gt;The fix is not a better cipher. It is a decision about coupling: the key and the ciphertext have to share a fate. Either both survive a restore or neither does. If the data syncs, the key has to be recoverable too, which usually means a syncable Keychain item or a passphrase the user can re-enter. If the key is truly device-bound, then the ciphertext must be device-bound as well, and you owe the user a loud, early warning that a new phone means starting over. I now write that fate-sharing rule on the same mental line as the &lt;code&gt;seal&lt;/code&gt; call, because the cipher never once failed me. The lifecycle did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you even need this? A counter-take on your own threat model
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable question I should have asked before writing any of it: what am I actually defending against? iOS already encrypts app data at rest. When the device is locked, files marked with Data Protection (&lt;code&gt;NSFileProtectionComplete&lt;/code&gt;) are sealed by a key tied to the passcode and the Secure Enclave. For a large class of personal apps, that is the real threat, a lost or stolen phone, and the OS has already handled it for free, more thoroughly than my hand-rolled layer does.&lt;/p&gt;

&lt;p&gt;So when does app-level AES-GCM earn its keep? In my experience, roughly three situations, and you should be honest about whether you are in one. First, the data leaves the device through a channel you do not trust and cannot mark protected, such as your own sync server or an export file. Second, the data lands in a shared container or an App Group where file protection is weaker than you assume. Third, you have a specific promise to keep, like "even I cannot read your notes," that the OS default does not make on your behalf. If none of those is true, adding encryption can be worse than adding nothing, because it hands you the key-loss failure from layer three in exchange for protection you already had. I still ship the layer, because one of those is true for me. I am genuinely not sure I would add it to a simpler app, and I would like to be argued into or out of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer four: the version byte you will wish you wrote
&lt;/h2&gt;

&lt;p&gt;The last layer is the cheapest to add and the most expensive to skip. Every encrypted blob I write starts with a single version byte in front of the CryptoKit &lt;code&gt;combined&lt;/code&gt; data. Right now it is always &lt;code&gt;0x01&lt;/code&gt;. It does nothing today. But the first time I need to rotate a key, change an accessibility class, or move from this construction to whatever replaces it, that byte is the difference between a clean migration and a guessing game against my own past self. When I read a blob back, the first thing the decryptor does is switch on that byte, before it ever hands anything to CryptoKit; an unknown version fails loudly and early instead of feeding garbage into &lt;code&gt;open&lt;/code&gt; and getting a confusing authentication error deep in the stack. Cryptographic formats are forever in the same way database schemas are forever, except you cannot read the rows to figure out what you meant. Write the version byte on day one. It is one byte.&lt;/p&gt;

&lt;p&gt;One more honest boundary, because it is easy to oversell what any of this buys. In my app the encryption happens inside the same single action that queues the note, right before it lands in the offline Outbox I described &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;in an earlier teardown&lt;/a&gt;. That protects the note at rest, on the device. The moment it goes out as an email, it is plaintext again, because email is plaintext. Encryption at rest is a statement about a stolen phone, not about the wire, and I say exactly that in the app's own copy, because a privacy claim you have to squint at is worse than none. Simple Memo is the small iOS app where I keep relearning that distinction. If pulling a subsystem apart like this is your thing, I did the same to the &lt;a href="https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp"&gt;cross-app write path&lt;/a&gt; in another post; the encryption here runs in the breath just before that write.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I had to answer for myself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need to store the nonce separately from the ciphertext?&lt;/strong&gt; No. The &lt;code&gt;combined&lt;/code&gt; property already puts the 12-byte nonce in front of the ciphertext and the tag behind it. Store that one blob, and &lt;code&gt;SealedBox(combined:)&lt;/code&gt; pulls the nonce back out on the way in. Storing it separately is extra surface area for a bug and buys you nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I keep the AES key in the Secure Enclave for maximum safety?&lt;/strong&gt; No, and this is the most common misconception I hear. The Secure Enclave holds P-256 keys and performs signing and key agreement only. To "use the Enclave" for symmetric encryption you generate a P-256 key inside it and use key agreement plus HKDF to wrap or derive your AES key, keeping only the wrapped form on disk. It is real protection, and it is more plumbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is CryptoKit's AES-GCM interoperable with my backend?&lt;/strong&gt; Usually yes, but check two things: the nonce length and the byte layout. CryptoKit uses a 12-byte nonce and appends the 16-byte tag; many server libraries expect the same 12-byte IV but hand you the tag as a separate output. If your decrypt fails on the server, it is almost always the tag sitting in a place the other side did not expect, not the cipher itself disagreeing.&lt;/p&gt;

&lt;h2&gt;
  
  
  So that is the whole stack
&lt;/h2&gt;

&lt;p&gt;Cipher, nonce, key, restore, threat model, version byte. The cipher was the only part I never had to touch twice.&lt;/p&gt;

&lt;p&gt;If you have shipped encryption in a small app, I want to know which layer drew blood for you. Was it a nonce you reused by being clever, a key that did not survive a restore, a threat model you could not actually name out loud, or a format you painted yourself into? I hit the restore one hardest, and I still suspect there is a fifth failure I have simply not paid for yet. Tell me which one you hit, or tell me I am overcomplicating a problem the OS already solved. Both are useful to me.&lt;/p&gt;




&lt;p&gt;I'm one person building one iOS app, and a surprising share of it is plumbing nobody sees, like an encryption layer whose entire job is to be boring and correct. I write here every few days about the unglamorous half of shipping something alone, mostly the parts I got wrong first. If you want to see what all that plumbing adds up to, &lt;a href="https://simplememofast.com" rel="noopener noreferrer"&gt;it's over here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>security</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How I chose a note format future-me and an LLM can both read</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 18 Aug 2026 13:14:59 +0000</pubDate>
      <link>https://dev.to/simple_memo/how-i-chose-a-note-format-future-me-and-an-llm-can-both-read-46d9</link>
      <guid>https://dev.to/simple_memo/how-i-chose-a-note-format-future-me-and-an-llm-can-both-read-46d9</guid>
      <description>&lt;p&gt;I am not going to tell you which note format I settled on until the end. I want to show you how I picked it, because the decision is reusable and the answer probably isn't. I have kept notes as a solo developer for about three years, and for most of that time they had exactly one reader: a slightly tired version of me, three weeks later, trying to remember why I changed something. Then the second reader showed up. Now I paste my own notes into Claude and Cursor as context two or three times a week, and the model reads them cold, with no idea what any of my private shorthand meant. The day I started doing that, some of my formats quietly fell apart.&lt;/p&gt;

&lt;p&gt;So I did the boring thing and ran an actual decision instead of drifting into a habit. Here is the whole thing, axes and weights and rejected options included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two constraints, ranked before I looked at any format:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture friction comes first. A format I won't use at the end of a bad day is worth nothing, no matter how elegant it reads.&lt;/li&gt;
&lt;li&gt;Machine-legibility comes second. The notes have to survive being pasted into a model with zero cleanup, or the second reader gets nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The five formats I actually weighed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Freeform prose.&lt;/strong&gt; Just write paragraphs. Lowest possible friction going in: you type what you think. The cost shows up later. Future-me can't skim a wall of prose, and a model handed three months of undated paragraphs struggles to tell where one thought ends and the next begins. It reads everything as one blurry context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-note structured metadata.&lt;/strong&gt; Every note gets a small header: a title field, tags, a type, a date, maybe a status. This is lovely to query and genuinely nice for a machine, which likes named fields. But the friction is brutal at exactly the wrong moment. To write one line about a bug, I first have to decide its title and its type and its tags. Most nights I just wouldn't, and an unwritten note beats a well-formatted one zero times out of a hundred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Zettelkasten.&lt;/strong&gt; Atomic notes, stable IDs, dense links between them. In theory the richest structure of the five, and the only one that builds a real graph. In practice it asks me to do link-maintenance forever, and I already ran this experiment and quit it. I hand-kept around 1,400 Luhmann-style IDs for three years and then deleted every one of them, and my notes got better, not worse. That result is still fresh, so Zettelkasten started this round with a scar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamped one-liners.&lt;/strong&gt; Plain text, one thought per line, each line beginning with a date and a concrete noun or verb. &lt;code&gt;2026-03-14 fixed cold-start regression: moved the keychain read off the launch path&lt;/code&gt;. No header, no fields, no links. It looks almost too simple to count as a system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question-and-answer notes.&lt;/strong&gt; Each note is a question with an answer underneath, like a private FAQ. Wonderful for the notes that are genuinely questions. Terrible ceremony for the eighty percent that are just an observation I want to park before it evaporates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which axes actually decide it?
&lt;/h2&gt;

&lt;p&gt;I scored the five on the five things I care about. Low, medium, high — where for maintenance cost, low is good.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Capture friction&lt;/th&gt;
&lt;th&gt;Machine-legibility&lt;/th&gt;
&lt;th&gt;Future-me skimming&lt;/th&gt;
&lt;th&gt;Graph / link value&lt;/th&gt;
&lt;th&gt;Maintenance cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Freeform prose&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured metadata&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zettelkasten&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamped one-liners&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Question-and-answer&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table alone doesn't pick a winner, because the columns are not worth the same. A format that scores high on graph value and high on friction is not a tie against one that scores low on both — it depends entirely on how much I weight friction. Which is the actual decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I weighted the axes
&lt;/h2&gt;

&lt;p&gt;I gave capture friction more weight than everything else combined. This is not a principled information-theory stance. It is a fact about me: I am one person, I capture at the worst moments, and any friction at capture time doesn't make my notes tidier, it makes them not exist. So friction dominates.&lt;/p&gt;

&lt;p&gt;Machine-legibility came second and it is the axis that changed since last year. A note is now something a model reads, and models turn out to have a clear preference that surprised me: they do not need my metadata nearly as much as they need my ordering and my verbs. A timestamp gives a model sequence for free. A leading verb gives it the topic of the line without a tags field. Plain dated lines read to a model almost like a commit log, and models are very good at commit logs.&lt;/p&gt;

&lt;p&gt;Future-me skimming came third, maintenance cost fourth as a penalty, and graph value came dead last. That last one is the deliberate, uncomfortable trade I want to name out loud: I am choosing a format that will never build me a navigable web of linked ideas. I am giving up the graph on purpose. I decided I almost never actually traverse a knowledge graph; what I actually do is search for a line and read the twenty lines around it. So I optimized for the thing I do, not the thing I once imagined I'd do.&lt;/p&gt;

&lt;p&gt;A concrete example of the weighting doing real work. Structured metadata scored high on machine-legibility and I was tempted, because a model does love a clean &lt;code&gt;type: bug, tags: [ios, launch]&lt;/code&gt; header. But run it through the weights and it collapses: the header buys a little legibility I mostly already get from the verb, and it costs friction on the one axis I refuse to spend on. A format can be objectively richer and still lose, because the axis it wins is the axis I priced cheap and the axis it loses is the one I priced above everything. Writing the weights down first is what stopped me from being seduced by the format that demos best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the machine really change the answer?
&lt;/h2&gt;

&lt;p&gt;It is worth being honest about how much the second reader actually moved the decision, because it would be easy to overstate it. Three years ago, with only future-me reading, I would probably have landed on timestamped lines anyway — skimmability and low friction were already pulling that way. What the model added was a second, independent vote for the same format, and it retired one candidate I might otherwise have kept flirting with. I used to assume that the day notes became machine-input, I'd need more structure, not less. The opposite happened. The model reads my least-structured format the most comfortably, because plain dated lines look like exactly the kind of sequential, terse log that these systems were trained on oceans of. The machine didn't ask me to build it a schema. It asked me to stop building one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually picked
&lt;/h2&gt;

&lt;p&gt;Timestamped one-liners. Plain text, one thought per line, each line led by a date and a concrete noun or verb, no per-note structure at all. The lines accumulate in a folder of dated markdown files, and that folder is the whole system. There is no schema to remember and nothing to maintain.&lt;/p&gt;

&lt;p&gt;It won capture friction outright, which was most of the weight, because writing a note is just typing a line. It won machine-legibility for a reason I didn't predict before I tested it: when I paste a month of these lines into a model, I paste them raw, no cleanup, and the model handles them better than it handled my old structured notes, because the timestamps give it order and the leading verbs give it topics. It won future-me skimming because a column of dated first-words is the most skimmable thing I've ever kept. It lost graph value completely, and I let it, because that was the trade I'd already decided to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did the other four lose?
&lt;/h2&gt;

&lt;p&gt;Structured metadata lost on the only axis I refused to compromise: it taxes the moment of capture, and taxed capture is skipped capture. Zettelkasten lost because I had already paid its maintenance cost for three years and &lt;a href="https://dev.to/simple_memo/i-deleted-every-zettelkasten-id-and-my-notes-got-better-fkd"&gt;written about deleting every one of those IDs&lt;/a&gt;; choosing it again would have meant un-learning my own result. Question-and-answer lost because it's a great format wearing the wrong uniform for daily use — I kept it, but only as a thing I reach for deliberately, not as the default. Freeform prose lost on both readers at once: unskimmable for me, unsegmentable for the model.&lt;/p&gt;

&lt;p&gt;None of these are bad formats. They lost against my specific weights. Someone who traverses a real graph every day, or who queries structured fields constantly, should weight the axes differently and would correctly pick something else. That's the point of showing the weights instead of just the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checks I'll run in three months
&lt;/h2&gt;

&lt;p&gt;A decision journal is worthless if you never check it, so here is how I'll know I chose wrong. Did I stop capturing? If the friction of even a dated line turns out to be too much on bad days, the format failed at its one job. Did a paste into a model need cleanup? If I find myself reformatting notes before they're useful as context, machine-legibility wasn't as high as I scored it. And did I ever badly want a link I didn't have? If I keep reaching for a connection between two lines and the flat format can't give it to me, then I underweighted graph value and I owe myself a rematch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I had to answer first
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Won't a model prefer clean structured data over plain lines?&lt;/strong&gt; I assumed yes and was wrong in practice. For long-context pasting, ordering and short topical verbs did more work than named fields, and they cost me nothing to produce. A model reading dated lines reconstructs the structure it needs; it does not need me to pre-chew it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't plain text going to rot without a system?&lt;/strong&gt; It would if it had no anchor. The timestamp is the system. Every line is findable by date and by its first word, which is all the retrieval I've ever actually performed on my own notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about tags?&lt;/strong&gt; I don't add them at capture time, because that's the friction I'm avoiding. When I need a category, I search for the verb. The leading word is a tag that costs nothing to write.&lt;/p&gt;

&lt;p&gt;If you keep notes for both a human and a machine now, I'd like to know: what is the one piece of structure you add at capture time that has actually paid off later when you fed the notes to a model — and what did you add once, feel clever about, and end up ripping out?&lt;/p&gt;




&lt;p&gt;I build a small iOS app by myself, and most of it is an argument that the moment of capture should cost almost nothing. I write here every few days about the parts of doing that alone that I'm still getting wrong. If you're curious where these dated lines eventually land, &lt;a href="https://simplememofast.com/obsidian/" rel="noopener noreferrer"&gt;it's here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>watercooler</category>
      <category>genai</category>
      <category>notes</category>
    </item>
    <item>
      <title>I hate marketing. Here are six things I did anyway.</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 14 Aug 2026 13:09:00 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-hate-marketing-here-are-six-things-i-did-anyway-1ohc</link>
      <guid>https://dev.to/simple_memo/i-hate-marketing-here-are-six-things-i-did-anyway-1ohc</guid>
      <description>&lt;p&gt;Six short pieces from my notebook this month, all about marketing. I have shipped a small iOS app by myself for about fourteen months, and marketing is still the job I schedule last and finish worst. These are not tactics. They are the notes I wrote to myself after each attempt, so that next-me would stop making the same face at the word.&lt;/p&gt;

&lt;h2&gt;
  
  
  The word is the problem, not the work
&lt;/h2&gt;

&lt;p&gt;I kept freezing on the task called "marketing." So I renamed it. Now my calendar says "tell one honest person one true thing," and I do it without the dread.&lt;/p&gt;

&lt;p&gt;The rename mattered more than any tactic. "Marketing" carries a picture: a funnel, a growth curve, someone louder than me winning. "Tell one person one true thing" carries a different picture, which is just a message I already know how to write. Same activity, on most days. The difference is that one of the two framings makes me open the laptop.&lt;/p&gt;

&lt;p&gt;I think the aversion a lot of builders feel is not to the act but to the costume. We imagine we have to become a person who says "growth hack" without wincing. We don't. The work underneath is closer to writing a good commit message: say what changed, say why it matters, keep it short. I can do that. I could never do the costume.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as marketing when you hate the word?
&lt;/h2&gt;

&lt;p&gt;For a week I logged everything that touched a stranger's awareness of the app, and then sorted it by whether it felt like "marketing" or not. The results embarrassed me.&lt;/p&gt;

&lt;p&gt;Answering a question in a forum, honestly, without linking anything: felt like being a person, counted as marketing. Fixing a confusing sentence in the App Store description: felt like editing, counted as marketing. Writing a plain post about a bug I caused: felt like confession, was probably my best marketing all month. Meanwhile the thing that felt the most like Marketing, a scheduled promotional post, did approximately nothing.&lt;/p&gt;

&lt;p&gt;The lesson I wrote down: the parts that work rarely feel like marketing, and the parts that feel like marketing rarely work. If a task makes me feel like a salesperson, that is weak evidence it is worth doing. If a task just makes me feel useful to one specific human, that is where the signal has been.&lt;/p&gt;

&lt;h2&gt;
  
  
  The launch is a coin, the habit is a wage
&lt;/h2&gt;

&lt;p&gt;I have launched loudly and I have shown up quietly, and I finally sat down to compare what each actually returns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;The loud launch&lt;/th&gt;
&lt;th&gt;The quiet habit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Effort shape&lt;/td&gt;
&lt;td&gt;One big spike, then nothing&lt;/td&gt;
&lt;td&gt;Small, daily, boring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to feedback&lt;/td&gt;
&lt;td&gt;Same day, then silence&lt;/td&gt;
&lt;td&gt;Slow, then compounding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who it reaches&lt;/td&gt;
&lt;td&gt;People who like launches&lt;/td&gt;
&lt;td&gt;People who have the problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How it feels&lt;/td&gt;
&lt;td&gt;Exposed&lt;/td&gt;
&lt;td&gt;Almost invisible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it leaves behind&lt;/td&gt;
&lt;td&gt;A spike in a chart&lt;/td&gt;
&lt;td&gt;A searchable trail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The loud launch is a coin flip: sometimes it lands, mostly it doesn't, and either way you learn little because the crowd that shows up for launches is not the crowd that keeps a tool. The quiet habit is a wage. It pays badly per hour and reliably over months, because each honest post or answer sits there and keeps getting found. I have stopped hoping for the coin. I show up for the wage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say the boring true thing before the clever one
&lt;/h2&gt;

&lt;p&gt;My best-performing sentence this year was not clever. It was: "It sends the note as an email and there is nowhere to file it." That is the whole product, stated flatly.&lt;/p&gt;

&lt;p&gt;I spent months trying to find a clever line, an angle, a hook that made a note-to-email app sound like a movement. Every clever line I wrote made the thing sound like more than it is, and people who arrived on a clever line left disappointed by the honest reality. The boring true sentence did the opposite. It set an expectation the app could actually meet, so the few people it drew in stayed.&lt;/p&gt;

&lt;p&gt;I think clever marketing is a tax you pay later, in refunds and churn and one-star reviews that all say "not what I expected." Boring-and-true is the cheaper long game. Describe the thing so plainly that the only people who come are the ones it will actually fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does talking about the work count as marketing?
&lt;/h2&gt;

&lt;p&gt;I was not sure, so I ran a small test: for a month I wrote publicly about the mechanics of building alone, with no pitch attached, and I watched what happened to the app I never mentioned by name.&lt;/p&gt;

&lt;p&gt;What happened was quiet and real. A handful of people found the app by following the trail of posts back to the person who wrote them. Nobody bought because of a call to action, because there wasn't one. They bought because they had read enough of my thinking to trust that the tool was built by someone who thought about the problem the way they did. I wrote more about that trade-off in &lt;a href="https://dev.to/simple_memo/five-things-shipping-a-product-alone-took-from-me-3ce0"&gt;an earlier note on what building solo takes from you&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So yes, talking about the work is marketing, and it is the kind I can sustain, because it does not require me to sell. It only requires me to be useful in public and let people draw their own line back to what I make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I still get wrong
&lt;/h2&gt;

&lt;p&gt;I under-market, and I know it, and knowing it has not fixed it. I will build a feature for three weeks and mention it once, quietly, on a Tuesday, and then feel obscurely virtuous about not being pushy.&lt;/p&gt;

&lt;p&gt;That is not humility. It is a comfortable way to avoid the discomfort of repetition. The honest truth is that most people who would like the thing have not heard of it, will not hear of it once, and will not remember it the first time even if they do. Marketing that hates repeating itself is just a hobby that produces posts. I am trying to say the same true things more than once without feeling like a fraud, and I am not there yet. If you have made peace with repeating yourself, I would like to know how.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I had to answer for myself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a big audience for any of this to work?&lt;/strong&gt; No. Every good outcome I traced this month came from one person reading one thing, not from reach. A small trail that the right person can follow beat a big spike every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't "just be honest" naive advice?&lt;/strong&gt; It would be if honest were easy. It is not. Saying the plain, unflattering truth about a small product takes more nerve than saying something clever, which is exactly why so little marketing does it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would I do first if I were starting over?&lt;/strong&gt; Write the one boring true sentence that describes the product, put it everywhere, and stop reaching for the clever version. Then answer questions in public until people know my face.&lt;/p&gt;

&lt;p&gt;I will keep writing these next month. There is a seventh piece already forming, about the difference between being visible and being findable, but it is not done thinking yet.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo developer, and Simple Memo is the little iOS thing I make and, it turns out, have to market: one tap turns a thought into an email you have already sent. I write here every few days about the awkward parts of doing every job alone. It &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;lives here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>indiehackers</category>
      <category>marketing</category>
      <category>solodev</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>The worst time to organize a note is the moment you take it</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:29:33 +0000</pubDate>
      <link>https://dev.to/simple_memo/the-worst-time-to-organize-a-note-is-the-moment-you-take-it-1fi9</link>
      <guid>https://dev.to/simple_memo/the-worst-time-to-organize-a-note-is-the-moment-you-take-it-1fi9</guid>
      <description>&lt;p&gt;I watched myself do it again last Tuesday. I caught a fix worth remembering, opened a note, and then spent the next ninety seconds deciding which folder it belonged in, what to tag it, and whether it was really a "task" or a "reference." Having the thought took about one second. Filing it took ninety. By the time I was done, the fix I had actually wanted to write down had gone soft at the edges, and I was no longer sure I had it right.&lt;/p&gt;

&lt;p&gt;That ninety seconds is the thing I want to argue about. Not because it is long, but because of &lt;em&gt;when&lt;/em&gt; it lands: at the exact instant I can least afford it.&lt;/p&gt;

&lt;p&gt;Here is the claim, and I will spend the rest of the post earning it. Capture and curation are two different jobs with opposite requirements, and almost every note tool I have used quietly asks me to run both at once. That is the mistake. Do them together and you get the worst of each: capture slow enough that thoughts slip away, and curation done in a one-second window with none of the context that makes it worth doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tools actually ask of you
&lt;/h2&gt;

&lt;p&gt;Open most capture flows and count the decisions before the thought is safe. Pick a notebook. Choose a project. Add tags. Set a type. Maybe a due date. Each of those is defensible on its own. Together they turn "write this down" into a small triage meeting, and they schedule that meeting for the one moment you are guaranteed to be busy: mid-thought, mid-task, mid-something.&lt;/p&gt;

&lt;p&gt;The thing the apps get wrong is that they treat the tags and the folders as &lt;em&gt;free&lt;/em&gt;, as if slotting a note into structure were part of writing it. It is not. It is a second, heavier job wearing the first job's clothes. And it is a job you are catastrophically bad at right now, because you are holding a thought in working memory and every keystroke spent on metadata is a keystroke not spent holding it.&lt;/p&gt;

&lt;p&gt;I have measured the cost of interruptions like this on myself before, and it is not linear. A &lt;a href="https://dev.to/simple_memo/a-15-second-interruption-cost-me-40-minutes-of-re-entry-4b5f"&gt;fifteen-second interruption that cost me forty minutes of re-entry&lt;/a&gt; taught me that the price of a context switch is not the switch, it is the climb back. Organizing at capture time is a context switch you volunteer for, on purpose, every single time you have an idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does doing both at once cost double?
&lt;/h2&gt;

&lt;p&gt;Because you are running curation blind.&lt;/p&gt;

&lt;p&gt;Curation is the act of deciding where a thought belongs and what it connects to. That decision is only good when you have information: what else is in the vault, what this relates to, whether it is still true next week. At capture time you have exactly none of that. You are deciding a note's permanent address at the one moment you know the least about it. So you either guess, and build a structure full of confident wrong guesses, or you stall for ninety seconds trying to guess well, and lose the thought you came to save.&lt;/p&gt;

&lt;p&gt;There is a reason &lt;a href="https://dev.to/simple_memo/a-thought-you-cant-capture-in-a-second-is-already-gone-20n9"&gt;a thought you cannot capture in about a second is already gone&lt;/a&gt;. The window is that short. Anything you bolt onto the front of capture is spending a budget that was never yours to spend.&lt;/p&gt;

&lt;p&gt;So the tax is double. You pay it once in lost thoughts, because capture got slow. You pay it again in a junk taxonomy, because curation got done at the worst possible time by the least informed version of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split I actually run
&lt;/h2&gt;

&lt;p&gt;Two jobs, two modes, and I keep them physically apart so I cannot cheat.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Capture&lt;/th&gt;
&lt;th&gt;Curation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When&lt;/td&gt;
&lt;td&gt;The instant a thought lands&lt;/td&gt;
&lt;td&gt;Once a week, deliberately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where&lt;/td&gt;
&lt;td&gt;Phone, wherever I am&lt;/td&gt;
&lt;td&gt;Laptop, at a desk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time budget&lt;/td&gt;
&lt;td&gt;About one second&lt;/td&gt;
&lt;td&gt;Ten to fifteen quiet minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mindset&lt;/td&gt;
&lt;td&gt;Dumb, fast, no judgment&lt;/td&gt;
&lt;td&gt;Slow, full of judgment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allowed actions&lt;/td&gt;
&lt;td&gt;One line, append it, done&lt;/td&gt;
&lt;td&gt;Read, connect, rename, discard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What kills it&lt;/td&gt;
&lt;td&gt;Any decision at all&lt;/td&gt;
&lt;td&gt;Skipping it for a month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Capture is append-only. One line, one action, timestamped by the world instead of by me, no folder, no tag, no type. The whole point of the tool I build for myself is that it opens in about a quarter of a second and turns a line into a saved, sent record before I have finished the thought. There is no place to put a tag because I removed the place to put a tag. That absence is a feature.&lt;/p&gt;

&lt;p&gt;Curation is a separate act I do later, on a different device, on purpose. Once a week I sit down at the laptop, read the week's raw lines in a plain markdown vault, and decide what graduates: what becomes its own note, what links to what, what was noise and gets deleted without ceremony. Now I have context. Now the judgment is worth something, because I am reading a thought next to the six thoughts that arrived after it.&lt;/p&gt;

&lt;p&gt;The device split does real work. If capture and curation lived in the same app on the same screen, I would drift into tidying at capture time out of sheer proximity. Making curation happen somewhere else raises the friction just enough that I stop trying to do it in the checkout line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks, and who should ignore me
&lt;/h2&gt;

&lt;p&gt;I owe you the cases where I am wrong, because a rant that only flatters its own thesis is just a mood.&lt;/p&gt;

&lt;p&gt;Dumb capture wins for a solo dev with a curation habit. It stops winning the moment your inbox is shared. If an untriaged item blocks a teammate, then "I'll sort it Sunday" is not discipline, it is a dropped handoff, and some structure at capture time is the price of not being the bottleneck. Team queues and personal inboxes are different animals, and I am only defending the personal one.&lt;/p&gt;

&lt;p&gt;It also breaks if you never actually curate. An append-only inbox you never revisit is not a system, it is a landfill with timestamps. The whole split is load-bearing on the back half. If you know yourself well enough to know the Sunday pass will not happen, then a little structure at capture time is a hedge against your own neglect, and that is a fair trade. I am not making it, but I see it.&lt;/p&gt;

&lt;p&gt;And there is a volume line somewhere. Capture a handful of lines a day and an unstructured inbox stays scannable. Capture two hundred and it does not, and you will want &lt;em&gt;some&lt;/em&gt; coarse bucket at the door just to keep the Sunday pass finishable. I have not hit that line as a team of one. If you have, your mileage is honestly different, not wrongly different.&lt;/p&gt;

&lt;h2&gt;
  
  
  A couple of questions I get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't a dumb inbox just inbox-zero with extra steps?&lt;/strong&gt; No, and the difference is the timestamp. Inbox-zero is a pressure to &lt;em&gt;clear&lt;/em&gt;. The dated line has no pressure at all, because nothing is overdue and nothing is unfiled, it is just a log. I am not racing a counter to zero. I am reading a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the one-second capture saves garbage?&lt;/strong&gt; It saves some, and I delete it on Sunday in about the time it would have taken to tag it on Tuesday. The asymmetry is the point: deletion is cheap and late and well-informed; tagging is expensive and early and blind. I would rather throw out ten cheap lines than misfile one expensive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one line I want from you
&lt;/h2&gt;

&lt;p&gt;I am curious where other people draw the exact border, because I suspect mine is slightly wrong and I cannot see which side.&lt;/p&gt;

&lt;p&gt;So, concretely: what is the &lt;em&gt;one&lt;/em&gt; thing you let yourself do to a note at the moment you capture it, and what is the one thing you had to forbid yourself from doing because it kept eating the thought? A single allowed move and a single banned one. I want to see where the line lands for work that isn't mine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Simple Memo is an iOS app I build by myself: type one line and it is a sent email before your thumb leaves the screen, with nowhere to file it and nothing to tag. I post here every few days about the mechanics of doing this solo. You can find it &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;on the App Store&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>writing</category>
      <category>notes</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The iOS sandbox turns 18: the ways out, oldest first</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 04 Aug 2026 13:31:24 +0000</pubDate>
      <link>https://dev.to/simple_memo/the-ios-sandbox-turns-18-the-ways-out-oldest-first-4a6f</link>
      <guid>https://dev.to/simple_memo/the-ios-sandbox-turns-18-the-ways-out-oldest-first-4a6f</guid>
      <description>&lt;p&gt;2008: the App Store opens, and every app you install is handed a sealed container it cannot reach out of. Eighteen years later, I still spend real time choosing the right sanctioned way to move one line of text between two iOS apps. This is that history, told oldest method first, by someone who has shipped most of it.&lt;/p&gt;

&lt;p&gt;I build a small iOS app on my own, and its entire job is to get a thought you just typed out of itself and somewhere useful before you lose it. That makes "getting data out of the sandbox" less an abstraction for me than the whole product. So I have opinions about every door Apple has cut into that wall, and I know roughly when each one appeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS has never let two apps share a filesystem. Every method below is a specific, Apple-sanctioned door punched through the sandbox wall, added one at a time.&lt;/li&gt;
&lt;li&gt;The doors moved in one clear direction over eighteen years: from invisible and automatic toward explicit and user-consented.&lt;/li&gt;
&lt;li&gt;Real file-level sharing did not exist until 2014. Everything before iOS 8 moved strings, not files.&lt;/li&gt;
&lt;li&gt;In 2026 I ship exactly two of these mechanisms, and they solve opposite problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why was moving data ever hard?
&lt;/h2&gt;

&lt;p&gt;On a desktop, "share data between two programs" is barely a sentence. Any process can read a file in your Documents folder, so the shared filesystem is the integration. iOS threw that assumption out on day one. Each app lives in its own container directory, walled off from every other app's storage, with no ambient shared folder and nothing resembling a common drive. The default is isolation, and it is a security feature, not an oversight.&lt;/p&gt;

&lt;p&gt;So on iOS, "share data" has never been one thing. It has always been a narrower question: which specific, blessed bridge do I use to move this particular payload from my container into somewhere another app or the user can reach? The rest of this piece is those bridges, in the order Apple shipped them, and what each one was actually good for.&lt;/p&gt;

&lt;h2&gt;
  
  
  2009: strings escape first
&lt;/h2&gt;

&lt;p&gt;Three ways out landed close together in iOS 3.0, and all three moved text, not files.&lt;/p&gt;

&lt;p&gt;The first was the clipboard. &lt;code&gt;UIPasteboard&lt;/code&gt; gave every app read and write access to a shared pasteboard, so a user could copy in app A and paste in app B. It was the crudest possible transfer and also the most universal, and for years it was completely silent: an app could read whatever you last copied without you knowing. Hold that thought, because it comes back to bite the whole model in 2020.&lt;/p&gt;

&lt;p&gt;The second was custom URL schemes. An app registered something like &lt;code&gt;myapp://&lt;/code&gt;, and any other app could hand it a small payload by opening that URL. It worked, and it was fragile in every direction. Any app could claim any scheme, so there was no ownership and no trust, and you could only pass what fit in a URL. It was duct tape, but it was the first way one app could deliberately poke another.&lt;/p&gt;

&lt;p&gt;The third was email, through &lt;code&gt;MFMailComposeViewController&lt;/code&gt; in the MessageUI framework. On the surface it is just a mail compose sheet you present inside your app. Underneath it is the strangest and most durable bridge on this list, because it is the only one that does not require the destination app to exist on the device at all. The data leaves the device entirely, lands on a server, and sits in a format that will still open in forty years. I will come back to why that turns out to matter more than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  2010 to 2012: files start to move
&lt;/h2&gt;

&lt;p&gt;iOS 3.2 (2010) gave us &lt;code&gt;UIDocumentInteractionController&lt;/code&gt;, better known as the "Open in..." menu. For the first time you could hand a whole file to another app, matched by its type, and let the user pick a target that had registered for it. It was one-way, always user-initiated, and it copied the file into the other app's container rather than sharing it. But it was the first time the unit of transfer was a document instead of a string.&lt;/p&gt;

&lt;p&gt;iOS 5.0 (2011) added something quieter that mattered enormously later: &lt;code&gt;NSFileCoordinator&lt;/code&gt; and &lt;code&gt;NSFilePresenter&lt;/code&gt;, which arrived alongside iCloud document storage. Their whole reason to exist is the idea that two processes might touch the same file, and that they need to take turns so nobody reads a half-written mess. Nobody demoed this at a keynote. It became load-bearing three years later.&lt;/p&gt;

&lt;p&gt;iOS 6.0 (2012) standardized the mess of "Open in..." into &lt;code&gt;UIActivityViewController&lt;/code&gt;, the share sheet. Instead of every app inventing its own export menu, there was now one system menu, and apps registered activities into it. It was still one payload at a time and still something the user had to start, but it turned a scattered set of one-off handoffs into a single, discoverable gesture that every iOS user now knows by heart.&lt;/p&gt;

&lt;h2&gt;
  
  
  2014: the year the sandbox grew real doors
&lt;/h2&gt;

&lt;p&gt;If this history has a hinge, it is iOS 8. Three things shipped together that finally made file-level sharing real.&lt;/p&gt;

&lt;p&gt;App Extensions let your code run inside another app's context: Share extensions, Action extensions, the Today widget, Document Provider extensions, and custom keyboards. Your app was no longer confined to its own window. App Groups gave your own apps and their extensions a genuinely shared container directory, the first sanctioned place two pieces of your own software could read and write the same files. Apple's own guidance came with a warning attached: when you write to that shared container, you must do it "in a coordinated manner to avoid data corruption." That is exactly why the sleepy 2011 file-coordination API suddenly showed up in everyone's code.&lt;/p&gt;

&lt;p&gt;The third piece was iCloud Drive together with &lt;code&gt;UIDocumentPickerViewController&lt;/code&gt; and security-scoped bookmarks. For the first time, an app could, with the user's explicit grant, reach into a folder living in another app's iCloud space and write there, then persist that permission across launches with a bookmark. This is the birth of the modern "append a line into another app's folder" path. I took that exact write path apart layer by layer in &lt;a href="https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp"&gt;a separate teardown&lt;/a&gt;; here the point is smaller. Before 2014 it was not possible, and after 2014 it was the sanctioned way.&lt;/p&gt;

&lt;h2&gt;
  
  
  2015 to 2017: links grow up, files become visible
&lt;/h2&gt;

&lt;p&gt;iOS 9 (2015) fixed the original sin of custom URL schemes with Universal Links: verified &lt;code&gt;https&lt;/code&gt; links that open your app if it is installed and fall back to your website if it is not. Because the link is a real web address you have proven you own, another app can no longer quietly claim it. The 2009 duct tape finally grew an identity.&lt;/p&gt;

&lt;p&gt;iOS 11 (2017) made the filesystem itself visible. The Files app and &lt;code&gt;UIDocumentBrowserViewController&lt;/code&gt; gave users a place to see and navigate documents across apps and cloud providers, and the document picker got simpler to adopt. The sandbox was still standing, but now there was a shared, user-facing surface, folders, that several apps could point at and a person could actually reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  2020 to 2022: the tide turns toward consent
&lt;/h2&gt;

&lt;p&gt;Then the direction reversed. iOS 14 (2020) made reading the pasteboard raise a banner, so the oldest and most invisible bridge on this list became the most conspicuous one. Apps that had been quietly sniffing the clipboard for years got caught on camera overnight.&lt;/p&gt;

&lt;p&gt;iOS 16 (2022) went further with an explicit "allow paste" prompt and &lt;code&gt;UIPasteControl&lt;/code&gt;, so an app reading the pasteboard without clear user intent now needs permission, and 16.1 added a per-app paste setting. For a decade Apple had spent its energy adding ways to move data. Now it was spending energy adding friction to the ones that moved data without you noticing. The wall was not getting shorter. The gates were getting louder.&lt;/p&gt;

&lt;p&gt;Here is the whole eighteen years on one screen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year (iOS)&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;What it moves&lt;/th&gt;
&lt;th&gt;Who starts it&lt;/th&gt;
&lt;th&gt;First-class in 2026?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2009 (3.0)&lt;/td&gt;
&lt;td&gt;UIPasteboard&lt;/td&gt;
&lt;td&gt;A string or blob&lt;/td&gt;
&lt;td&gt;User copies&lt;/td&gt;
&lt;td&gt;Yes, but policed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2009 (3.0)&lt;/td&gt;
&lt;td&gt;Custom URL schemes&lt;/td&gt;
&lt;td&gt;A small URL payload&lt;/td&gt;
&lt;td&gt;Either app&lt;/td&gt;
&lt;td&gt;Superseded by Universal Links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2009 (3.0)&lt;/td&gt;
&lt;td&gt;MFMailComposeViewController&lt;/td&gt;
&lt;td&gt;A whole message, off device&lt;/td&gt;
&lt;td&gt;User confirms send&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2010 (3.2)&lt;/td&gt;
&lt;td&gt;UIDocumentInteractionController&lt;/td&gt;
&lt;td&gt;One file, by type&lt;/td&gt;
&lt;td&gt;User picks "Open in"&lt;/td&gt;
&lt;td&gt;Mostly folded into the share sheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2011 (5.0)&lt;/td&gt;
&lt;td&gt;NSFileCoordinator&lt;/td&gt;
&lt;td&gt;Safe access to a shared file&lt;/td&gt;
&lt;td&gt;Your code&lt;/td&gt;
&lt;td&gt;Yes, under the hood&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2012 (6.0)&lt;/td&gt;
&lt;td&gt;UIActivityViewController&lt;/td&gt;
&lt;td&gt;One payload, anywhere&lt;/td&gt;
&lt;td&gt;User opens share sheet&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2014 (8.0)&lt;/td&gt;
&lt;td&gt;App Groups container&lt;/td&gt;
&lt;td&gt;Files, between your own apps&lt;/td&gt;
&lt;td&gt;Your code&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2014 (8.0)&lt;/td&gt;
&lt;td&gt;iCloud Drive + document picker&lt;/td&gt;
&lt;td&gt;Into a user-chosen folder&lt;/td&gt;
&lt;td&gt;User grants, code writes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2015 (9.0)&lt;/td&gt;
&lt;td&gt;Universal Links&lt;/td&gt;
&lt;td&gt;An https route into your app&lt;/td&gt;
&lt;td&gt;Either app&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2017 (11.0)&lt;/td&gt;
&lt;td&gt;Files app + document browser&lt;/td&gt;
&lt;td&gt;Files, user-visible&lt;/td&gt;
&lt;td&gt;User&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020–22 (14–16)&lt;/td&gt;
&lt;td&gt;Pasteboard consent, UIPasteControl&lt;/td&gt;
&lt;td&gt;Gatekeeping, not transport&lt;/td&gt;
&lt;td&gt;User consents&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So which of these do I still reach for?
&lt;/h2&gt;

&lt;p&gt;Two of them, and they solve opposite problems.&lt;/p&gt;

&lt;p&gt;The first is email, through &lt;code&gt;MFMailComposeViewController&lt;/code&gt;, the bridge from 2009. I keep coming back to it because it is the only mechanism on the whole list that does not care whether the other app exists. The line leaves the device, lands in an inbox the user already checks every day, and sits there in a format nothing can deprecate. I wrap the send in a small offline queue so a failed attempt retries instead of evaporating, which is a whole design problem of its own that I wrote up as &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;an offline-first outbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second is the iCloud Drive document-picker path from 2014, for the case where the destination is a folder the user already owns on their own device. My app uses it to append a dated line to a Markdown vault, which is the one place Obsidian users tend to notice it, and the coordinated write is the only thing standing between "your note got appended" and "your file got corrupted."&lt;/p&gt;

&lt;p&gt;Notice what the two survivors have in common. In both, the destination is the user's own storage or account, an inbox and a folder they control, not another app's private container. The bridges that tried to reach directly into someone else's sandbox are the ones I have quietly stopped using.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't email a strange way to move data between apps in 2026?&lt;/strong&gt; It looks quaint right up until you notice it is the only mechanism here that works when the target app is not installed, works offline if you queue it, and leaves behind an artifact that will still open decades from now. Quaint and durable are not opposites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just use the clipboard for everything?&lt;/strong&gt; Because after iOS 14 the clipboard is the most watched channel on the system. It is genuinely good for a user-initiated copy and genuinely wrong for anything your app does on its own. Treat the privacy banner as a design constraint you were handed, not a bug to route around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do App Intents and Shortcuts fit?&lt;/strong&gt; They are the newest door, and I left them off the timeline on purpose, because they move actions rather than just data and they deserve their own history. But they are plainly where the direction of travel points: user-authored automation over routes hard-wired by an app developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does this history go next?
&lt;/h2&gt;

&lt;p&gt;The eighteen-year arc has one shape. Every generation of cross-app data movement added either user visibility or user consent to the generation before it. The invisible clipboard gave way to a discoverable share sheet, which gave way to an explicit document picker, which gave way to an outright paste prompt. Nothing on this list got easier to do silently. Almost everything got easier to do honestly.&lt;/p&gt;

&lt;p&gt;If that trend holds, the next chapter is not another file API. It is intent, App Intents and Shortcuts and on-device models, working out where a piece of data should go and asking you to confirm, instead of an app quietly hard-wiring the route years in advance.&lt;/p&gt;

&lt;p&gt;Which leaves me with a real question rather than a rhetorical one. What is the oldest cross-app mechanism you still ship in production, a custom URL scheme, an "Open in" handler, a raw pasteboard read, and what finally made you replace it, or decide that you never would? I am trying to sort out which of my own two survivors are principled choices and which are just habit wearing a nice coat, and your holdouts would help me tell the difference.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm the solo developer behind Simple Memo, an iOS app that turns a line you type into an email about a second later, and on the days I curate, appends that same line to a Markdown vault on iCloud. I write here about the unglamorous parts of building it alone. The vault side of that is documented at simplememofast.com/obsidian/.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>apple</category>
      <category>iosdev</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Driving vs. delegating: I timed two ways of pairing with AI</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 31 Jul 2026 13:33:09 +0000</pubDate>
      <link>https://dev.to/simple_memo/driving-vs-delegating-i-timed-two-ways-of-pairing-with-ai-3pn9</link>
      <guid>https://dev.to/simple_memo/driving-vs-delegating-i-timed-two-ways-of-pairing-with-ai-3pn9</guid>
      <description>&lt;p&gt;There are two ways I pair with an AI coding assistant, and for most of a year I could only have told you which one felt faster, not which one was. One afternoon in June I let an agent rewrite a 300-line module in about a minute, then spent the next two hours cleaning up after it. The size of that gap, and the fact that I had been ignoring it, is the whole reason I sat down to write this.&lt;/p&gt;

&lt;p&gt;By driving I mean keeping my hands on the code. I write most of it, and the model works at the scale of a line or a function: a completion here, a "what's the signature for this API" there, a second read on something I already wrote. By delegating I mean the opposite posture. I describe a whole task, hand it off, walk away, and come back to review a diff I did not write. Same assistant, same me, same afternoon. Two completely different relationships to the code that comes out the other end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delegating a whole task to an agent is front-loaded speed and back-loaded cost. The diff lands in seconds, but the work just moves downstream into review, and review is exactly where the model's wrong assumptions hide. Driving is the reverse: slow to type, cheap to trust.&lt;/li&gt;
&lt;li&gt;The axis that actually decides which one is faster is not task size. It is verifiability. I delegate when I can check the result faster than I could have written it, and I drive when checking is as expensive as authoring. The rest of this is me arriving at that one sentence the slow way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What does "faster" even mean here?
&lt;/h2&gt;

&lt;p&gt;The reason I could not answer the speed question for a year is that I was conflating two different clocks.&lt;/p&gt;

&lt;p&gt;There is the clock that runs while the code appears. By that clock, delegating wins every time and it isn't close. I type three sentences of intent, and forty seconds later a diff touches six files. Driving cannot compete with that, and my nervous system knows it: a big diff scrolling past feels like progress in a way that writing one careful function never does.&lt;/p&gt;

&lt;p&gt;Then there is the clock that runs until the code is actually correct and I trust it. That clock includes reading the diff, catching what the model quietly assumed, discovering the integration bug three days later, and re-explaining the constraint it ignored the first time. On that clock the ranking often flips, and the flip is invisible in the moment because the two clocks report at different times. Delegating bills you now and charges you later. I had been reading only the first invoice.&lt;/p&gt;

&lt;p&gt;So the honest version of "which is faster" is a question about the second clock, and the second clock is dominated by one thing: how long it takes me to verify the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two postures, side by side
&lt;/h2&gt;

&lt;p&gt;Here is the comparison after tracking it deliberately across a few dozen tasks this spring, in a solo iOS codebase with no team to absorb the review load:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Driving&lt;/th&gt;
&lt;th&gt;Delegating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;My role&lt;/td&gt;
&lt;td&gt;Author, model assists&lt;/td&gt;
&lt;td&gt;Reviewer, agent authors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feels fast?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, dangerously so&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the time goes&lt;/td&gt;
&lt;td&gt;Typing, up front&lt;/td&gt;
&lt;td&gt;Review and rework, after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mental model of the code&lt;/td&gt;
&lt;td&gt;I hold it the whole time&lt;/td&gt;
&lt;td&gt;I have to reconstruct it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best at&lt;/td&gt;
&lt;td&gt;Tasks where checking is as hard as writing&lt;/td&gt;
&lt;td&gt;Tasks I can verify at a glance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst failure&lt;/td&gt;
&lt;td&gt;I'm slow on work a model could have done&lt;/td&gt;
&lt;td&gt;A wrong assumption ships past a shallow review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost visibility&lt;/td&gt;
&lt;td&gt;Obvious and immediate&lt;/td&gt;
&lt;td&gt;Hidden and deferred&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The row I care about most is the last one. Driving fails loudly: I feel every slow minute, so I never over-trust it. Delegating fails quietly, and quiet failures are the expensive kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where delegating clearly wins
&lt;/h2&gt;

&lt;p&gt;When the task is cheap to verify, delegating is not just faster, it is obviously the right call, and I have stopped feeling clever about driving through work like this.&lt;/p&gt;

&lt;p&gt;A new isolated SwiftUI settings screen is the clean example. The correctness is visible: I run it, I look at it, I tab through the fields. If the agent got the layout wrong I see it in five seconds, and the cost of being wrong is a re-render, not a corrupted file. Scaffolding a parser with a table of example inputs and expected outputs is another, because the examples are the check. So is a mechanical refactor, renaming a concept across forty call sites, where the compiler is a far better reviewer than I am and the agent is faster at the typing than I will ever be.&lt;/p&gt;

&lt;p&gt;The pattern under all three is the same. The output carries its own test. I can look at the result and know it is right faster than I could have produced it, so handing off the production is pure profit. This is most of the surface area of a normal codebase, and pretending otherwise, insisting on writing every line by hand, is just craft nostalgia with a time cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where driving quietly wins
&lt;/h2&gt;

&lt;p&gt;Then there is the other kind of task, where the code is not the expensive part.&lt;/p&gt;

&lt;p&gt;The expensive part is noticing the one wrong assumption, and you only notice it if you were already holding the whole model in your head. My Outbox is the case that taught me this. It is a small offline-first queue that retries failed sends, and I have &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;written before about how carefully it has to behave&lt;/a&gt; when the network flaps and the app is backgrounded mid-flush. When I delegated a change to its retry path, the agent produced clean, plausible, well-named code that added jitter to the backoff exactly as I asked. It also, without mentioning it, moved a state write outside the lock that was the only thing keeping two retries from waking on the same tick.&lt;/p&gt;

&lt;p&gt;That is not a bug you catch by reading a diff for style. You catch it by having spent a week inside the concurrency model months ago and feeling the wrongness before you can even articulate it. Reviewing that diff cost me more attention than writing the change would have, because to review it properly I had to rebuild the exact mental model that driving would have kept warm the entire time. The same held for the code that appends a line to another app's iCloud folder, where a &lt;a href="https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp"&gt;subtle mistake in the coordinated-write path&lt;/a&gt; is silent until someone's note goes missing.&lt;/p&gt;

&lt;p&gt;For that class of work, delegating does not save time. It just relocates the hard part from a place where I am strong, authoring with the model in my head, to a place where I am weak, spotting a buried assumption in someone else's plausible code.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is the actual rule?
&lt;/h2&gt;

&lt;p&gt;For a long time I thought the axis was task size: small things by hand, big things to the agent. That is exactly backwards, and it cost me to learn it.&lt;/p&gt;

&lt;p&gt;The real axis is verification cost. Delegate when you can check the result faster than you could write it. Drive when checking is as hard as writing. Task size barely matters; a 300-line SwiftUI view can be cheaper to verify than a nine-line change to a lock. What I am really doing when I choose a posture is comparing two numbers I can now feel without measuring: the cost to produce, and the cost to check. When check is much less than produce, I hand it off and don't look back. When check approaches produce, I keep my hands on the wheel, because the model's speed is buying me nothing I can safely accept.&lt;/p&gt;

&lt;p&gt;This is also why the fashionable framing, "just get better at prompting and you can delegate everything," misses the point. The bottleneck was never how well I could specify the task. I can specify the Outbox change down to the last detail, and the review is still the expensive part, because an airtight spec does not make a subtle violation of it cheap to detect. The limit is verification, not instruction. It is the same reason I keep &lt;a href="https://dev.to/simple_memo/the-prompt-is-the-cheap-part-the-context-is-the-product-dh0"&gt;context, not prompts, at the center of how I work with a model&lt;/a&gt;: the leverage is in what the model and I can hold in common, not in the wording of the ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap I keep falling into
&lt;/h2&gt;

&lt;p&gt;Knowing the rule does not make me immune to it, and I want to be honest about that rather than end on a tidy law.&lt;/p&gt;

&lt;p&gt;The pull toward delegating is not really about speed. It is that a large diff feels like accomplishment, and driving feels like labor, and on a tired afternoon I will reach for the thing that feels like accomplishment even when the task is one I should be holding myself. I have shipped a quiet bug this way more than once, each time on a day when I let the good feeling of a fast diff stand in for the boring question of whether I could actually check it. The tell, when I catch it in time, is that I am about to approve a diff I could not have written, in a part of the system where being wrong is expensive. That specific combination is my signal to stop, close the diff, and drive.&lt;/p&gt;

&lt;p&gt;So the two-way choice has become a one-question habit. Before I accept a handoff I ask whether I can verify this more cheaply than I could have built it. If yes, the agent keeps the work. If I hesitate, the hesitation is the answer, and I take the wheel back.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't this just "use AI for boilerplate"?&lt;/strong&gt; Close, but boilerplate is a proxy, and a leaky one. Some boilerplate is expensive to verify, such as security-sensitive glue, and some novel code is trivial to verify, like a pure function with obvious outputs. Verification cost is the real variable; "boilerplate" is just the case where it is usually low.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't delegating more make you worse at the hard parts over time?&lt;/strong&gt; It is my honest worry. My hedge is that the hard parts, the ones where checking is as costly as writing, are exactly the ones the rule tells me to keep driving. If the rule holds, I stay sharp precisely where sharpness is load-bearing and offload only where it isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does an agent's autonomy fit?&lt;/strong&gt; Same axis, higher stakes. The more autonomously it runs, the more code accrues before I review, and the more verification debt piles up if I can't check it cheaply. Long autonomous runs are wonderful for verifiable work and quietly dangerous for the other kind, for exactly the reasons above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open question I actually want answered
&lt;/h2&gt;

&lt;p&gt;Here is the thing I am still missing, and it is specific. When you delegate a real task to an agent, what is the single check you run before you trust the diff? Not "I read it carefully," but the concrete move: the test you write first, the invariant you assert, the one file you always open, the question you ask the model about its own output. My whole approach now rests on making verification cheap, and I am fairly sure the people who delegate well have a verification move I haven't stolen yet. I would rather learn yours than defend mine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Simple Memo is the one-person iOS app I make: type a line and it lands in your email a second or so later, so the thought is caught before it evaporates. I post here every few days about what shipping solo keeps teaching me, this piece included. What I'm building lives at &lt;a href="https://simplememofast.com/" rel="noopener noreferrer"&gt;simplememofast.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I killed every meeting as a solo dev. Half of it backfired.</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 28 Jul 2026 13:37:13 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-killed-every-meeting-as-a-solo-dev-half-of-it-backfired-58ei</link>
      <guid>https://dev.to/simple_memo/i-killed-every-meeting-as-a-solo-dev-half-of-it-backfired-58ei</guid>
      <description>&lt;p&gt;The line I was proudest of, for about 14 months, was that I ran a product with zero meetings. No standups. No syncs. Not one calendar invite. I said it the way people announce they have quit caffeine: a little smug, waiting for the flinch of envy.&lt;/p&gt;

&lt;p&gt;Then I put one meeting back. A single recurring call, once a month. And the month after I added it, I closed a decision I had been circling for three weeks. The timing nagged at me enough that I went back through my own notes to work out what the meetings I had so proudly deleted were actually doing. The answer was not flattering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero meetings as a solo dev was half right: the coordination meetings I deleted really were dead weight, because there was nobody on the other side to coordinate with.&lt;/li&gt;
&lt;li&gt;It was half wrong. Meetings also force decisions, put a witness on your commitments, and drop a hard stop on the workday. Those jobs did not vanish when the invites did. I just stopped paying for them on purpose and started paying interest instead.&lt;/li&gt;
&lt;li&gt;The fix was not reinstating meetings. It was rebuilding those specific jobs deliberately, with one standing call, a timeboxed weekly review, and a habit of deciding in public.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The boast, and where I picked it up
&lt;/h2&gt;

&lt;p&gt;I did not invent the anti-meeting position. I inherited it, fully formed, from people I respect.&lt;/p&gt;

&lt;p&gt;Paul Graham's 2009 essay "Maker's Schedule, Manager's Schedule" taught a generation of builders that a maker and a manager run on different clocks, and that a single midday meeting can blow a crater in an afternoon by splitting it into two pieces too small to build anything in. A year later, Jason Fried and David Heinemeier Hansson gave the movement its bumper sticker in &lt;em&gt;Rework&lt;/em&gt;, in a section literally titled "Meetings Are Toxic," where they pointed out that a one-hour meeting with five people is not one hour, it is five. I read both in my twenties and treated them as settled law.&lt;/p&gt;

&lt;p&gt;So when I went solo, dropping meetings felt less like a choice and more like finally getting to practice what I had preached. And here is the part I still stand behind: for a solo developer, most meetings genuinely are waste. A standup coordinates handoffs between people. A sprint planning session negotiates capacity across a team. A status meeting exists so that eight humans can build one shared picture of where things are. When the team is one person, all of that machinery is spinning against no load. It is the same reason I eventually stopped using an &lt;a href="https://dev.to/simple_memo/the-issue-tracker-was-built-for-a-team-i-dont-have-4pli"&gt;issue tracker built for a team I don't have&lt;/a&gt;: the tool was solving a coordination problem I no longer had.&lt;/p&gt;

&lt;p&gt;That much was right, and I want to be clear about it before I take the rest apart. Deleting coordination meetings cost me nothing, because there was nothing there to coordinate.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what were the meetings really doing?
&lt;/h2&gt;

&lt;p&gt;The mistake was assuming coordination was the &lt;em&gt;only&lt;/em&gt; thing meetings did. It was the most visible thing, so I concluded it was the whole thing. It wasn't.&lt;/p&gt;

&lt;p&gt;When I traced my worst weeks back to a cause, the same few functions kept showing up, and every one of them was a job a meeting used to do off to the side, while everyone thought the point of the meeting was the agenda.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What a meeting also did&lt;/th&gt;
&lt;th&gt;My solo substitute&lt;/th&gt;
&lt;th&gt;What it actually cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Forced a decision by a hard date&lt;/td&gt;
&lt;td&gt;"I'll decide once I've thought it through properly"&lt;/td&gt;
&lt;td&gt;Decisions drifted for weeks; a deadline had been doing work I never credited it for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put a witness on my commitments&lt;/td&gt;
&lt;td&gt;Kept the plan in my own head&lt;/td&gt;
&lt;td&gt;I quietly abandoned plans nobody had seen me make&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surfaced disagreement in real time&lt;/td&gt;
&lt;td&gt;Argued both sides with myself on paper&lt;/td&gt;
&lt;td&gt;I shipped confident mistakes with no one to catch them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dropped a hard stop on the day&lt;/td&gt;
&lt;td&gt;Worked until I drifted off it&lt;/td&gt;
&lt;td&gt;The task sprawled to fill every hour I gave it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are coordination. All of them are load-bearing. And unlike the coordination overhead, which I could delete for free, these four were structural. Removing the meeting removed the structure and left the need standing there with nothing to lean on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three weeks I lost to a decision nobody forced
&lt;/h2&gt;

&lt;p&gt;Here is the one that finally got my attention, because it was expensive and entirely my own fault.&lt;/p&gt;

&lt;p&gt;I had a schema change to make. Small, reversible, the sort of thing that in a previous job would have come up in a Tuesday sync, gotten fifteen minutes of mild argument, and been decided by Wednesday because the meeting simply ended and a choice had to exist by then. On my own, with no meeting to end, the decision had no deadline attached to it. So I did the responsible-sounding thing and kept thinking about it.&lt;/p&gt;

&lt;p&gt;I thought about it for three weeks. I wrote two documents weighing the options. I felt productive the whole time, because writing documents feels like work. But the schema did not change for 21 days, and the feature sitting on top of it did not ship, and the actual amount of new information I gathered in week three versus week one was close to zero. I had not been deliberating. I had been marinating.&lt;/p&gt;

&lt;p&gt;A meeting would have killed that decision in a day, not because meetings are smart but because they are &lt;em&gt;over&lt;/em&gt; at a specific time, and a decision that has to exist by the time a meeting ends tends to exist. I had thrown away the deadline along with the small talk. This is the thing the anti-meeting essays are quietest about: for someone like me, the meeting was never the interruption. The meeting was the forcing function, and the calendar was doing my executive function for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed without reopening my calendar
&lt;/h2&gt;

&lt;p&gt;I want to be honest that this is where my zero-meetings boast became literally false, and I think that is fine. The goal was never zero meetings. The goal was zero &lt;em&gt;waste&lt;/em&gt;. I had confused the two.&lt;/p&gt;

&lt;p&gt;I rebuilt those functions on purpose, and only those. The forcing function and the hard stop came back together, as clocks. A standing weekly review, 30 minutes with a timer running, where any decision I have been "thinking about" for more than a week has to resolve or get explicitly killed, plus a fixed end-of-day I hold to so the work stops sprawling into the evening. The witness came back as deciding in public. When I commit to something now, I write it somewhere a reader could see it, because a plan with a witness behaves differently from a plan I am keeping in my head, even when the witness is a handful of strangers who will never follow up.&lt;/p&gt;

&lt;p&gt;And the disagreement came back as the one real meeting, the monthly call, with another solo developer who is under no obligation to be nice to me. I had already written about losing &lt;a href="https://dev.to/simple_memo/five-things-shipping-a-product-alone-took-from-me-3ce0"&gt;the one person who tells you you're wrong&lt;/a&gt; when you ship alone. The meeting is where that person used to live. Rebuilding a thin version of them was worth exactly one calendar invite a month.&lt;/p&gt;

&lt;p&gt;Notice what I did not add back: a standup, a status meeting, anything recurring whose job is coordination. Those stayed dead, because those really were waste. I paid to restore the functions I had lost and refused to pay for the ones I hadn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isn't this just meetings with extra steps?
&lt;/h2&gt;

&lt;p&gt;This is the fair objection, and I have argued it against myself more than once, so let me put it at full strength.&lt;/p&gt;

&lt;p&gt;You could say I have simply reinvented meetings, badly, and that my "timeboxed review" is a standup with a party of one, my "deciding in public" is a status update, and my monthly call is just a meeting I have decided to feel clever about. There is a version of this where I spent 14 months taking the machine apart only to rebuild most of it and claim I invented something.&lt;/p&gt;

&lt;p&gt;I think that objection is about 60% right, and the honest 40% is this. The reconstructed versions are strictly weaker than the real thing at exactly one job, disagreement, and that weakness is not fixable. When I argue both sides of a decision on paper, I am still the one holding the pen, and I cannot fully surprise myself. The monthly call helps, but it is one hour against a whole month of me agreeing with me. I do not actually know yet whether that call is doing real work or whether it is a comfort blanket I have talked myself into needing. Ask me in a year. Forcing, witnessing, and stopping all rebuild cleanly, because they are mechanical and do not need a second brain. Disagreement is the one that genuinely wants another human, and it is the one I am still faking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I think I'm wrong next
&lt;/h2&gt;

&lt;p&gt;If the pattern holds, the belief I am currently too proud of is that async always beats sync. I have spent this whole post defending a slower, written, meeting-light way of working, and I still believe it for most things. But I have started to notice a specific class of problem, usually a tangled decision with three or four interacting unknowns, where 20 minutes of live back-and-forth with one person gets me further than a week of careful async ever does. I suspect I am still underpricing synchronous bandwidth for exactly the decisions that are too knotty to write down cleanly, and that in a year I will be back here explaining which half of &lt;em&gt;this&lt;/em&gt; post was wrong.&lt;/p&gt;

&lt;p&gt;That is the actual lesson, if there is one. Not "meetings good" or "meetings bad." Just that when you delete a ritual, you should figure out every job it was silently doing first, because some of those jobs do not have the decency to announce themselves on the way out.&lt;/p&gt;

&lt;h2&gt;
  
  
  A couple of questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Doesn't a weekly self-review just become another thing you skip?&lt;/strong&gt; It did, until I put a hard external date on it by tying it to the same day I do my monthly numbers, which I will not skip because money is involved. Attaching the soft ritual to a hard one was the only version that survived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just get better discipline and decide faster without any of this?&lt;/strong&gt; Because "get better discipline" is the plan that has failed every single time I have tried it, and building a structure that makes the right thing happen on a schedule is the plan that works. I would rather engineer the forcing function than keep losing to my own good intentions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you had employees?&lt;/strong&gt; Then most of what I deleted would come straight back, because it would be doing real coordination work again, and this entire post would be irrelevant. That is the point. The right number of meetings is a function of how many people have to stay in sync, and for a team of one that number is small, but it is not zero.&lt;/p&gt;

&lt;p&gt;Here is the question I would actually like answered: when you removed a meeting, a standup, a weekly sync, a recurring call, what job did it turn out to be doing that you only noticed once it was gone? I am collecting these, because I suspect forcing, witnessing, and stopping are not the only three.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm a solo developer building an iOS app called Simple Memo. It &lt;a href="https://simplememofast.com/" rel="noopener noreferrer"&gt;turns a thought into a sent email&lt;/a&gt; before I can talk myself out of it. I write here every few days about the parts of working alone that nobody warns you about.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>management</category>
      <category>sideprojects</category>
      <category>remote</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
