<?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: Der Sascha</title>
    <description>The latest articles on DEV Community by Der Sascha (@saschadev).</description>
    <link>https://dev.to/saschadev</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%2F17720%2Fc9f0d090-121a-4a20-b572-98842a2e8de7.jpg</url>
      <title>DEV Community: Der Sascha</title>
      <link>https://dev.to/saschadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saschadev"/>
    <language>en</language>
    <item>
      <title>Stop logging PII: a configurable Node.js sanitizer logger</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Sun, 12 Jul 2026 05:02:45 +0000</pubDate>
      <link>https://dev.to/saschadev/stop-logging-pii-a-configurable-nodejs-sanitizer-logger-3c99</link>
      <guid>https://dev.to/saschadev/stop-logging-pii-a-configurable-nodejs-sanitizer-logger-3c99</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/stop-logging-pii-a-configurable-node-js-sanitizer-logger/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/stop-logging-pii-a-configurable-node-js-sanitizer-logger/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Logging is one of those topics that looks harmless until it is not.&lt;/p&gt;

&lt;p&gt;A developer adds a request object to a debug statement. A payment error includes a card number. A support workflow logs an email address, a phone number, and a bearer token because "we only need it for troubleshooting." Two months later those logs are in a SIEM, a data lake, three alert rules, and a backup nobody remembers.&lt;/p&gt;

&lt;p&gt;That is the part that bothers me about PII in logs: the first mistake is small, but the copies multiply quietly.&lt;/p&gt;

&lt;p&gt;So I built a small Node.js example that sanitizes data at the logging boundary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SBajonczak/PiiSanitizer" rel="noopener noreferrer"&gt;github.com/SBajonczak/PiiSanitizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is simple: before anything leaves your application as a log line, it passes through a configurable sanitizer. Common PII gets masked. Known sensitive object keys get redacted. Domain-specific identifiers can be configured without changing the logger code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the logger does
&lt;/h2&gt;

&lt;p&gt;The repository currently provides a lightweight Node.js module with no runtime dependencies.&lt;/p&gt;

&lt;p&gt;It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mask email addresses, phone numbers, IBAN-like values, and credit card numbers&lt;/li&gt;
&lt;li&gt;redact sensitive keys such as &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;authorization&lt;/code&gt;, and &lt;code&gt;apiKey&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;walk nested objects and arrays without mutating the original input&lt;/li&gt;
&lt;li&gt;emit either object records or JSON lines&lt;/li&gt;
&lt;li&gt;accept custom rules for application-specific identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important design choice is that configuration owns the sensitive patterns. The logger should not need a new release every time a project discovers a new internal ID format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;

&lt;p&gt;Here is the smallest useful example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPiiLogger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPiiLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Login for jane.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;correct-horse-battery-staple&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+49 170 1234567&lt;/span&gt;&lt;span class="dl"&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 output is safe to ship to container logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-14T12:51:45.760Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Login for [EMAIL]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[PHONE]"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is deliberately boring. Good logging infrastructure should be boring. The exciting part is everything that does not leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring domain-specific rules
&lt;/h2&gt;

&lt;p&gt;Every company has identifiers that do not look sensitive to a generic library.&lt;/p&gt;

&lt;p&gt;In SAP-heavy landscapes, for example, a personnel number may show up as &lt;code&gt;PERNR 12345678&lt;/code&gt;. Depending on the context, that can absolutely be personal data. A generic email masker will not catch it.&lt;/p&gt;

&lt;p&gt;So the sanitizer supports custom regex rules and key rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPiiLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultRules&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPiiLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rules&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="nx"&gt;defaultRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sapPersonnelNumber&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;regex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;bPERNR[ -]?&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;d{8}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[SAP_PERSONNEL_NUMBER]&lt;/span&gt;&lt;span class="dl"&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;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;employeeIdKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;employeeId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pernr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[EMPLOYEE_ID]&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User jane.doe@example.com opened PERNR 12345678&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;employeeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;12345678&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer never-log-this&lt;/span&gt;&lt;span class="dl"&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;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"User [EMAIL] opened [SAP_PERSONNEL_NUMBER]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"employeeId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[EMPLOYEE_ID]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key rule is intentionally separate from the regex rule. Sometimes the value itself is harmless without context, but the field name makes it sensitive. A number called &lt;code&gt;employeeId&lt;/code&gt; should be treated differently from the same number inside &lt;code&gt;items[0].quantity&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why sanitize at the logger boundary?
&lt;/h2&gt;

&lt;p&gt;You can try to make every developer remember what not to log.&lt;/p&gt;

&lt;p&gt;I would not bet a privacy incident on that.&lt;/p&gt;

&lt;p&gt;The safer pattern is a boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application code
  -&amp;gt; logger
  -&amp;gt; sanitizer
  -&amp;gt; stdout / log collector / SIEM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application code can still make mistakes. The sanitizer catches the common ones before they leave the process.&lt;/p&gt;

&lt;p&gt;This does not remove the need for good logging discipline. I still would not log raw HTTP requests, raw SAP payloads, payroll data, banking data, or identity documents. But a logger-level sanitizer gives you a last line of defense against accidental leakage.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few implementation details
&lt;/h2&gt;

&lt;p&gt;The sanitizer walks values recursively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jane.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+49 170 1234567&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jane Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[EMAIL]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[REDACTED]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[PHONE]&lt;/span&gt;&lt;span class="dl"&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original object is not mutated. That matters because logging should not change application state.&lt;/p&gt;

&lt;p&gt;Credit card masking uses a Luhn check, so the sanitizer does not replace every long number it sees. That reduces false positives in boring business data, which is important if people are expected to keep this enabled.&lt;/p&gt;

&lt;p&gt;Key matching ignores case, spaces, underscores, and dashes. These should all match the same rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;accessToken
access_token
Access Token
access-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tests first, because this kind of code needs trust
&lt;/h2&gt;

&lt;p&gt;I built the example test-first with Node's built-in test runner. No Jest, no Vitest, no dependency tree just to prove the point.&lt;/p&gt;

&lt;p&gt;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;masking common PII in strings&lt;/li&gt;
&lt;li&gt;recursive object sanitization&lt;/li&gt;
&lt;li&gt;custom SAP-style identifier rules&lt;/li&gt;
&lt;li&gt;logger output through a custom sink&lt;/li&gt;
&lt;li&gt;JSON-line logging for container environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also run the example directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;This is not a magic GDPR shield.&lt;/p&gt;

&lt;p&gt;It does not decide whether you are allowed to process data. It does not classify every possible personal attribute. It does not replace data minimization, retention policies, access control, or a proper DPIA when one is needed.&lt;/p&gt;

&lt;p&gt;It is a practical guardrail for a common failure mode: sensitive values accidentally ending up in logs.&lt;/p&gt;

&lt;p&gt;And honestly, that is already useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I would take it next
&lt;/h2&gt;

&lt;p&gt;The current repository is a small working foundation. The next useful steps would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;package it properly for npm&lt;/li&gt;
&lt;li&gt;add TypeScript type definitions&lt;/li&gt;
&lt;li&gt;add adapters for popular loggers like Pino and Winston&lt;/li&gt;
&lt;li&gt;add rule presets for common enterprise domains&lt;/li&gt;
&lt;li&gt;add structured audit metadata so teams can see which rules fired&lt;/li&gt;
&lt;li&gt;add benchmarks before anyone puts it on a hot path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the core idea will stay the same: log what helps you operate the system, not whatever happened to be nearby in memory.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/SBajonczak/PiiSanitizer" rel="noopener noreferrer"&gt;github.com/SBajonczak/PiiSanitizer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>privacy</category>
      <category>node</category>
    </item>
    <item>
      <title>How to Logout from Azure B2C on all Services correctly</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:59:45 +0000</pubDate>
      <link>https://dev.to/saschadev/how-to-logout-from-azure-b2c-on-all-services-correctly-1096</link>
      <guid>https://dev.to/saschadev/how-to-logout-from-azure-b2c-on-all-services-correctly-1096</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/how-to-logout-from-azure-b2c-on-all-services-correctly/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/how-to-logout-from-azure-b2c-on-all-services-correctly/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Azure B2C is an enterprise-scale B2C System. It comes with different tiers and also a free tier, so that will be interesting for every developer that wants authentication support with Facebook, Google, Amazon, or other Identity providers (IDP). &lt;/p&gt;
&lt;p&gt;Most developers will generate a Login Flow and get the Token. When he wants to log out, he must only clear the cookie that will be created and remove the session from the browser cache. Then the current user is logged out. This scenario is a common scenario for many applications. Still, a few enterprise applications will act as a single point of entry and will then "redirect" to other services in your architecture.&lt;/p&gt;
&lt;p&gt;The solution above (delete the cookie and remove the session) will not work. &lt;/p&gt;
&lt;h2 id="the-problem-in-multiapplication-architecture"&gt;The problem in multiapplication architecture&lt;/h2&gt;
&lt;p&gt;Let's assume you have an application as a "portal" that will redirect to some other services. This service will support single sign-on (SSO) through our application. &lt;/p&gt;
&lt;p&gt;So now the user (User A) will get logged in, navigate through your other service, and will be signed in automatically. After a while, the user will go to your portal application again and sign out. Next on the same machine, a colleague (User B) will log on to the portal. After this, he will navigate to the service again and then you will recognize that User A was signed in instead of User B. &lt;/p&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;h2 id="what-happened-here"&gt;What happened here? &lt;/h2&gt;
&lt;p&gt;In your portal, you have removed only your authentication information from your portal. But the other service generated a separate cookie too. This cookie was not deleted by your code, so the service will keep the first logged-in user. Only when you actively sign off from the target system, you will get logged off.&lt;/p&gt;
&lt;p&gt;"What? I must go through each portal to sign off?" you will ask now? &amp;nbsp;&lt;/p&gt;
&lt;p&gt;Technically yes, because every system does its own logout routine. So it will be better to call this sign-off function.&lt;/p&gt;
&lt;h2 id="azure-b2c-solution-1"&gt;Azure B2C Solution 1&lt;/h2&gt;
&lt;p&gt;Instead of navigating through each logout URL of each service, &amp;nbsp;Azure B2C hosts a single endpoint for this logout routine. Microsoft has a &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect#send-a-sign-out-request" rel="noopener noreferrer"&gt;documentation page&lt;/a&gt; about this Url. So in simple, you must navigate through this URL&lt;/p&gt;
&lt;blockquote&gt;https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{policy}/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Fjwt.ms%2&lt;/blockquote&gt;
&lt;p&gt;What Azure B2C then will do is create on the Azure B2C Logoutpage a hidden Iframe. In this, it will navigate through each registered application and will call the defined logout URL. Once it is done, it will redirect you to the URL given by the parameter post_logout_redirect_uri.&lt;/p&gt;
&lt;h2 id="azure-b2c-solution-2"&gt;Azure B2C Solution 2 &lt;/h2&gt;
&lt;p&gt;Instead of waiting for the redirect and so on, you can do it asynchronously by calling a &lt;a href="https://learn.microsoft.com/en-us/graph/api/user-invalidateallrefreshtokens?view=graph-rest-beta&amp;amp;tabs=http" rel="noopener noreferrer"&gt;graph API method&lt;/a&gt; to revoke all login sessions for the current (or a specific) user. &lt;/p&gt;
&lt;p&gt;When you want to logout by yourself and revoke your own session, you will be able to call only &lt;/p&gt;
&lt;blockquote&gt;&lt;a href="https://graph.microsoft.com/beta/me/invalidateAllRefreshTokens" rel="noopener noreferrer"&gt;https://graph.microsoft.com/beta/me/invalidateAllRefreshTokens&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Please pay attention to delivering an actual OAuth token in the Authorization header for the current user. After the call is successful you will get an HTTP 204 back. That means that your call was successful. &lt;/p&gt;
&lt;p&gt;The Graph API will give you also the possibility to log out a specific user by modifying the URL like this &lt;/p&gt;
&lt;blockquote&gt;&lt;a href="https://graph.microsoft.com/beta/users/{id%20|%20userPrincipalName}/invalidateAllRefreshTokens" rel="noopener noreferrer"&gt;https://graph.microsoft.com/beta/users/{id | userPrincipalName}/invalidateAllRefreshTokens&lt;/a&gt;&lt;/blockquote&gt;
&lt;p&gt;Instead of "Me" you will set the internal known user id to identify which session to revoke. &lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Yes Single Sign On works very well, it is quickly configured and it works then. But when you work with multiple applications you will get faced with a logout problem. This problem will prevent Azure B2C nice and smooth with the logout process. But when you want full control of the user sessions, you will be able to do this via the Office Graph API. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>security</category>
      <category>oauth</category>
      <category>b2c</category>
    </item>
    <item>
      <title>Implementing a captive portal for your ESP device</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:59:39 +0000</pubDate>
      <link>https://dev.to/saschadev/implementing-a-captive-portal-for-your-esp-device-41pd</link>
      <guid>https://dev.to/saschadev/implementing-a-captive-portal-for-your-esp-device-41pd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/implementing-a-captive-portal-for-your-esp-device/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/implementing-a-captive-portal-for-your-esp-device/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's consider you are in a cafè and you want to go to the internet. You recognize that you can use the free wifi, so you select the wifi and a nice looking page comes up and prompt you for some information like email phone number and so on. This Portal is called a captive portal.&lt;/p&gt;
&lt;p&gt;In one of my famous side project the Hivescale for my bees I use this captive portal to configure some specific for a tare or the first configuration like ... yes wifi connection and so on. This app is running on a small esp device and this will get some telemetry for my hives. So let me introduce you to how you can create a simple captive portal for your requirements.&lt;/p&gt;
&lt;h1 id="required-libraries"&gt;Required libraries&lt;/h1&gt;
&lt;p&gt;Now that the meaning and application of a Captive Portal are clear, now let's do the practical way. I will guide you through a piece of code that you can adapt to your needs. &amp;nbsp;First of all, we need a webserver that will host our website to represent the portal itself. I use for this the &lt;a href="https://github.com/me-no-dev/AsyncTCP" rel="noopener noreferrer"&gt;ESPAsyncWebserver&lt;/a&gt; library.&lt;/p&gt;
&lt;blockquote&gt;Please note the complete code can be found here: &lt;a href="https://gist.github.com/SBajonczak/737310ee2c2360439e0daa75cad805b2" rel="noopener noreferrer"&gt;Captive Portal (github.com)&lt;/a&gt;
&lt;/blockquote&gt;
&lt;p&gt;First we must include the libraries that we need to run&lt;/p&gt;
&lt;p&gt;Next, we need some variables:&lt;/p&gt;
&lt;p&gt;The AsyncWebServer is defined on port 80, which is the HTTP port.&lt;/p&gt;
&lt;p&gt;Next, we literally define the HTML file as a raw string literal in the Program Memory called PROGMEM). It contains a form essentially, which asks the user for a name and ESP32 Proficiency:&lt;/p&gt;
&lt;p&gt;Next, we create a CaptiveRequestHandler class which is inherited from the AsyncWebHandler class. Within the class, we have the two functions: canHandle() and handleRequest().&lt;/p&gt;
&lt;p&gt;The canHandle() method simply returns true here, meaning that our captive portal can handle any request.&lt;/p&gt;
&lt;p&gt;Next, the handleRequest function tells the AsyncWebServer what to do when a request is received. This is the function that is responsible for the captive portal opening up every time you connect to the WiFi of ESP32. We simply return the index_html page that we defined above (in our PROGMEM). Please note that send_P is used instead of send because we are fetching a large web page from the Program memory (PROGMEM).&lt;/p&gt;
&lt;p&gt;The next function is very important. We set up theAsyncWebServer to respond to various requests, process the data received in the request and send a reply:&lt;/p&gt;
&lt;p&gt;What we doing is to tell the server that when there is a GET request for the ‘/’ path, then redirect the user to index_html, and it receives a GET request for the ‘/get’ path (this will be at the time of form submission), then look for the name and proficiency in the response, and extract and store those fields is available, and send an acknowledgment to the user.&lt;/p&gt;
&lt;p&gt;Note that “name” and “proficiency” are the names given to the input fields in index_html.&lt;/p&gt;
&lt;p&gt;Also, note that the above function tells the server how to handle requests irrespective of the Captive Portal. In other words, if you connect to the ESP32’s WiFi field and enter the IP address in the browser, you’ll still be served by the index_html page, and if you fill out the form, your request will still be processed.&lt;/p&gt;
&lt;p&gt;Next, in the setup() function, we initialize Serial, set the WiFi mode to Access Point (this helps create the WiFi field around ESP32), and name the WiFi field “my-captive”.&lt;/p&gt;
&lt;p&gt;Then we set up the AsyncWebServer using the setupServer command. The next part is important. We set up and start the DNS server with the following line:&lt;/p&gt;
&lt;p&gt;dnsServer.start(53, “*”, WiFi.softAPIP());&lt;/p&gt;
&lt;p&gt;What does this mean? For that, we first have to understand the meaning of a DNS server. DNS stands for ‘Domain Name System’. When you type &lt;a href="https://www.google.com/" rel="noopener noreferrer"&gt;www.google.com&lt;/a&gt; in your web browser, the request gets sent to a DNS Server first, which translates this domain name into an IP Address, and then redirects the user to the corresponding IP address. On ESP32 we are creating our own DNS Server, and redirecting any domain name (*) to the IP of ESP32 (WiFi.softAPIP()). 53 stands for the port number. Just like HTTP requests are executed over port 80, HTTPS over port 443, DNS requests are executed over port 53.&lt;/p&gt;
&lt;p&gt;So, what happens exactly? How does the system work? Every time your device connects to a new network, it tried to access a specific URL.&lt;/p&gt;
&lt;p&gt;Which URL? That depends on your device. If it gets a response, it concludes that the internet is connected, else you see a message like “This WiFi has no access to the internet”.Now, when the device tries to connect with the ESP32’s WiFi, it again tries to send a request to a specific URL. The ESP32’s DNS server redirects it to the Soft AP IP address of ESP32, because remember, we are redirecting all domain names to this IP. On this IP, the captive request handler takes care of displaying the webpage, and the Async Web Server (set up using setupServer()), handles all the incoming request.&lt;/p&gt;
&lt;p&gt;Of course, the next line is important, and last piece of the jigsaw puzzle. It links the Async Web Server with the Captive Request Handler, only when the request is in the Access Point mode.&lt;/p&gt;
&lt;p&gt;server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP With the system ready, we begin the server.&lt;/p&gt;
&lt;p&gt;Within the loop, we just keep processing the requests made to the dnsServer and, if name and proficiency are received, we print them.&lt;/p&gt;
&lt;h1 id="results"&gt;Results:&lt;/h1&gt;
&lt;p&gt;Once you upload this code on the ESP32, the screenshots below will illustrate how the Captive Portal Works, and also the series of prints on the Serial Monitor. &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/p&gt;
&lt;p&gt;Connecting to ESP32’s Wi-Fi field using a mobile phone&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage.png" alt="" width="799" height="356"&gt;&lt;p&gt;I hope you had fun creating the captive portal using ESP32!&lt;/p&gt;

</description>
      <category>iot</category>
      <category>esp32</category>
      <category>arduino</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Extending my IoT Doorbell with Alexa features</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:59:33 +0000</pubDate>
      <link>https://dev.to/saschadev/extending-my-iot-doorbell-with-alexa-features-hkp</link>
      <guid>https://dev.to/saschadev/extending-my-iot-doorbell-with-alexa-features-hkp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/extending-my-iot-doorbell-with-alexa-features/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/extending-my-iot-doorbell-with-alexa-features/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my post about &lt;a href="https://blog.bajonczak.com/how-to-make-your-doorbell-smart/" rel="noopener noreferrer"&gt;how I got my doorbell smart&lt;/a&gt;, I thought... yes How can I extend it for now with some handy features? &lt;/p&gt;
&lt;p&gt;So, in fact, the bell is loud, but what is it when I am in my garden and my door is closed and the delivery guy must hold a heavy package and try to ring the doorbell I do not open it because I didn't hear the bell. That's frustrating for everyone and may end up like this&lt;/p&gt;


&lt;p&gt;&lt;a href="https://giphy.com/gifs/images-fedex-bbTIQvcZfSMFO" rel="noopener noreferrer"&gt;via GIPHY&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;So I must be get noticed when the doorbell rings, for this I can use one of my Alexas, especially the Alexa dot. In my Garden, there is one for playing music while I work in the garden. &lt;/p&gt;
&lt;p&gt;Thankfully I got Homeassistant as support for my Homeserver and it's controlling my House to automatically the most daily routines. So why not the notification for the doorbell?&lt;/p&gt;
&lt;h1 id="what-is-homeassistant"&gt;What is Homeassistant&lt;/h1&gt;
&lt;p&gt;For those that don't hear anything about Homeassistant let me introduce this to you.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Homeassistant&lt;/a&gt; is a nice and handy tool that will allow you to control absolutely everything in your home (not only there). It integrates with most provided devices and custom ones (like ESP easy and so on). &lt;/p&gt;
&lt;p&gt;On Top, there it comes with a nice handy web frontend that will allow you to create nice fancy (mobile-friendly) dashboards.  &lt;/p&gt;
&lt;p&gt;On the other site, you can create your custom automation, depending on actors like wall switches or calculated measures like sundown time. &lt;/p&gt;
&lt;p&gt;The best thing is, that it is so small, that you can install onto a raspberry pi, or you can install it via container into your Synology NAS. &lt;/p&gt;
&lt;p&gt;But the downside of this is, that this will get a Hobby that will overwhelms you very quickly, so be careful full ;)&lt;/p&gt;
&lt;h1 id="adding-the-extension-to-communicate-with-the-alexa-devices"&gt;Adding the extension to communicate with the Alexa devices&lt;/h1&gt;
&lt;p&gt;Homeassistant has plenty of extensions available. So one of them is the Alexa media player. This will discover then all Alexa devices in your amazon account and will enlist them than in your Homeassistant. &lt;/p&gt;
&lt;p&gt;First of all, add the integration into your Homeassistant&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-8.png" alt="" width="511" height="218"&gt;&lt;p&gt;Next, it will require the log in credentials for your amazon account. It will then generate an SSO Token to get connected through your amazon account to send messages to your Alexa devices and also discover telemetry data from these.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-9.png" alt="" width="456" height="856"&gt;
&lt;p&gt;Once installed, you will see in the integration section of Homeassistant a nice tile with the Alexa Media player and their discovered devices.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-10.png" alt="" width="258" height="198"&gt;&lt;p&gt;Also, it will enlist also a multiroom device. A Multi-room device is a group of devices that you can combine together. For example, you can create a multiroom called "everywhere" and add every Alexa you have to this multiroom. For my requirement, it will be perfect, because I want to get noticed everywhere, not only in the garden. Let's assume I am on the roof behind a closed door, there is no chance to hearing the doorbell, but there is an Alexa too, for playing music, while I iron my clothes (yes I do it myself ;)). &lt;/p&gt;

&lt;h1 id="our-first-try"&gt;Our first try&lt;/h1&gt;
&lt;p&gt;First, try out that your connection is correct and you can make a Text to Speech message through your Alexa. For this go to your Homeasssistant under &lt;a href="https://www.home-assistant.io/docs/tools/dev-tools/" rel="noopener noreferrer"&gt;Development tools&lt;/a&gt; and then on Services. Switch now to the Yaml mode and paste in the following lines of YAML code.&lt;/p&gt;

&lt;p&gt;Let me explain the first line. This will use the "notify"- action and address the destination after the first dot. &lt;/p&gt;
&lt;p&gt;The template for this is always "notify.alexa_media_{device-/multiroomname}". So this case we send it to the multiroom device &lt;strong&gt;everywhere.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The rest is self-explained.&lt;/p&gt;
&lt;p&gt;So when you hit the button execute service, you will get noticed by your Alexa. &lt;/p&gt;
&lt;h1 id="lets-integrate"&gt;Let's integrate&lt;/h1&gt;
&lt;p&gt;After our first test will be successful we can integrate them into our Homeassistant for now. Instead of creating awful hacky scripts, I will use simple automation for this. &lt;/p&gt;
&lt;p&gt;So go to Settings and then Automations and create a new one. As a trigger, you can use anything you have available. My doorbell sends a message to the MQTT on a specific topic. So I will listen to it. Here's my trigger definition&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-11.png" alt="" width="799" height="243"&gt;&lt;p&gt;The YAML definition is here&lt;/p&gt;

&lt;p&gt;After we have a trigger, there must execute our call, so go to the actions and add a new one. This will then execute Text to Speech action like above. So I modified the text for our announcement a little bit and it will look like this.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-13.png" alt="" width="800" height="413"&gt;&lt;p&gt;Please note, that I live in Germany, so my device name differs from the one above. The important part is the data part. Here I will tell Homeassistant to use the announcement and use the voice to announce (with speech). So the yaml for this action looks like this:&lt;/p&gt;

&lt;p&gt;Now you can save them and hit execute. After that, you will hear your Alexa telling you that someone is at your front door. &lt;/p&gt;
&lt;h1 id="final-words"&gt;Final Words&lt;/h1&gt;
&lt;p&gt;So I now have my custom digitalized doorbell extended with the functionality to speak.  So in that case I am in my garden or in a room, where I cannot hear the doorbell, I will get noticed through Alexa. After a little tryout with my homeassistant, I found this very helpful. Yes sure there are other ways too, but this was very seamless for me. &lt;/p&gt;
&lt;p&gt;The other thing is, that I now have a use case to integrate my Alexa devices into my Homeassistant for now. Sure you can use these Alexa announcements for more than the doorbell notification. Maybe for presence detection, to let Alexa greet them when they come into the door. But for me, it is enough that Alexa will tell me that the package delivery guy (or another person) is at the front door.  &lt;/p&gt;

</description>
      <category>iot</category>
      <category>homeassistant</category>
      <category>alexa</category>
      <category>esp32</category>
    </item>
    <item>
      <title>How I pimp my PowerShell</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:59:27 +0000</pubDate>
      <link>https://dev.to/saschadev/how-i-pimp-my-powershell-2mh5</link>
      <guid>https://dev.to/saschadev/how-i-pimp-my-powershell-2mh5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/how-i-pimp-my-shell/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/how-i-pimp-my-shell/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;p&gt;Every developer uses the shell, but why use the old-fashioned style of the shell.&lt;/p&gt;
&lt;p&gt;I prefer to use PowerShell, of course, you can use bash and other terminals instead. But this article is written for this (it can be also applied to another shell too).&lt;/p&gt;
&lt;h1 id="my-goals"&gt;My goals&lt;/h1&gt;
&lt;p&gt;To the fact, that I use git repos, I want to know the following information about the current directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On which Origin I am working&lt;/li&gt;
&lt;li&gt;in which branch I am working on&lt;/li&gt;
&lt;li&gt;Are there actual changes&lt;/li&gt;
&lt;li&gt;Is there s.th. to push&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So actually I want this Information in my (Power)Shell CMD.&lt;/p&gt;
&lt;h1 id="using-oh-my-posh"&gt;Using Oh My Posh&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://ohmyposh.dev/docs/" rel="noopener ugc nofollow noreferrer"&gt;Oh my Posh&lt;/a&gt; is a pendant to oh my bash. It is an extension for your shell that will gather some information about what you will need. It’s configurable and also themeable. So that you can design your shell (later) on your needs.&lt;/p&gt;
&lt;p&gt;So first of all, I must install the shell itself:# Windows&lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;p&gt;After that you have successfully installed you can immediately execute &lt;code&gt;oh-my-posh.exe&lt;/code&gt;&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F1%2Aps6LHskWuTqnHsm02U1bzQ.png" alt="" width="700" height="109"&gt;&lt;p&gt;Yes, you look right, it’s a binary executable. But now worry about it, we now integrate it now in your shell&lt;/p&gt;
&lt;h1 id="integrating-it-into-your-existing-shell"&gt;Integrating it into your existing shell&lt;/h1&gt;
&lt;p&gt;So the executable is nice to have, but we want the information just in time while we open up the cmd line and on every single prompt.&lt;/p&gt;
&lt;p&gt;So let’s integrate it into your (Power)Shell. Luckily you can use in the Shell a profile to integrate custom scripts, so we open up the profile in vs code withcode $PROFILE&lt;/p&gt;
&lt;p&gt;In some cases, there does not exist any profile, so it will create one. with the name &lt;code&gt;Microsoft.PowerShell_Profile.ps1&lt;/code&gt; . Maybe some profile contains some content, in this case, you can attach the next lines below the existing scripts (if you don’t care about the order of executions).&lt;/p&gt;
&lt;p&gt;First of all, you must import the required PowerShell Modules for oh-my-posh:Import-Module oh-my-posh&lt;/p&gt;
&lt;p&gt;When you will ensure that you install the module before it will be imported, you can use this at the top of the profileif ((Get-Module -ListAvailable -Name oh-my-posh) -eq $null) {Install-Module oh-my-posh -Scope CurrentUser -AllowPrerelease}&lt;/p&gt;
&lt;p&gt;Now now you can add the command that must be executed before each command line will be shown up, let’s use the default one, to see quick resultsoh-my-posh --init --shell pwsh --config ~/jandedobbeleer.omp.json | Invoke-Expression&lt;/p&gt;
&lt;p&gt;This command will execute oh-my-posh and uses the default config “jandedobbeleer.omp.json”.&lt;/p&gt;
&lt;p&gt;Now execute &lt;code&gt;. $PROFILE&lt;/code&gt; to reload the profile itself, to take effect.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F1%2AV-1HIl6Ca91ZgubQNP065Q.png" alt="" width="700" height="243"&gt;&lt;p&gt;So far so good, but the shell is very…yeah … ugly. Because there are some squares in it and that looks very unfriendly.&lt;/p&gt;
&lt;p&gt;This is because you use a font, that does not support any symbols that oh-my-posh uses. To solve this, I use &lt;strong&gt;&lt;strong&gt;Caskaydia Cove&lt;/strong&gt;&lt;/strong&gt; from &lt;a href="https://www.nerdfonts.com/" rel="noopener ugc nofollow noreferrer"&gt;Nerdfonts&lt;/a&gt;. This font collection uses the “free” font spaces and fills them up with some fonts from Fontawesome, Devicons, and so on. For more information about please go to the page.&lt;/p&gt;
&lt;p&gt;After assigning the new font, and a quick restart of the shell, you get the new look like this&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F1%2A-Ra8TVSN5TiBoZ5ah6qX-Q.png" alt="" width="700" height="162"&gt;&lt;h1 id="set-custom-configuration"&gt;Set custom configuration&lt;/h1&gt;
&lt;p&gt;Now the default prompt has some information that we don't want. So let’s customize this to our needs (defined above). Modify the cmd in your profile to use a custom configuration file:oh-my-posh --init --shell pwsh --config $HOME/ohmyposh/myconfig.omp.json | Invoke-Expression&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Now let’s open up the config &lt;code&gt;code $HOME/ohmyposh/myconfig.omp.json&lt;/code&gt; and past the default config into it:&lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;p&gt;After a reload of the config, &lt;code&gt;. $PROFILE&lt;/code&gt; you will be prompted with a minimal shell.&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F1%2AZnF7IIuU0ct3nX2GXLKNSg.png" alt="" width="700" height="55"&gt;&lt;h1 id="customize-the-cmd-line"&gt;Customize the cmd line&lt;/h1&gt;
&lt;p&gt;The first thing we customize is the shell itself. So I want the current time, the current directory first. This will do in the &lt;code&gt;segments&lt;/code&gt; area in the JSON configuration file. So each information will be a segment. Let’s add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The current time&lt;/li&gt;
&lt;li&gt;The current folder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I added a segment for the current time (and formatting of these)&lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;p&gt;After this I added this segment to display the current folder:&lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;p&gt;Now it will look like this:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F1%2AYBjwI2PHs7xa80_yd7R3Zw.png" alt="" width="700" height="30"&gt;&lt;h1 id="add-git-information"&gt;Add git Information&lt;/h1&gt;
&lt;p&gt;To add the git information you will add the git segment for this:&lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;p&gt;This contains some custom formatting. In general, the segment will display the changes with the following representation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;+&lt;/code&gt; Added&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~&lt;/code&gt; Modified&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt; Deleted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?&lt;/code&gt; Untracked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will be displayed as follow:&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F430%2F1%2AKbP5A8SkKEYOHCMfZBgx_A.png" alt="" width="430" height="22"&gt;&lt;p&gt;It will now display that the folder has open edits. 4 new Files that must be added and 8 Files that will be deleted.&lt;/p&gt;
&lt;p&gt;After this, you have all information you will need. You can also add more segments, have a look at the existing segment definitions &lt;a href="https://ohmyposh.dev/docs/angular" rel="noopener ugc nofollow noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="expert-level-tooltips"&gt;Expert level: Tooltips&lt;/h1&gt;
&lt;p&gt;So maybe if you like to add more and more segments, your command will get messy. In that case, you can use Tooltips instead.&lt;/p&gt;
&lt;p&gt;To enable them you must add &lt;code&gt;Enable-PoshTooltips&lt;/code&gt; after the oh-my-posh command. That will rebuild the tooltip collection.&lt;/p&gt;
&lt;p&gt;Tooltips will display the extension depending on your typed command. Here is an example for git&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F0%2A4wq29dKP0VgibD2D.gif" alt="" width="800" height="289"&gt;&lt;p&gt;To add a tooltip you must add it into the &lt;code&gt;tooltips&lt;/code&gt; area in the configuration.&lt;/p&gt;
&lt;p&gt;So for git, it will look like this:"tooltips": &lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;&lt;p&gt;so a tooltip uses the properties from the segment definition like above, but it will extend the Properties&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tips&lt;/li&gt;
&lt;li&gt;leading_diamond&lt;/li&gt;
&lt;li&gt;trailing_diamond&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In &lt;code&gt;tips&lt;/code&gt;you can enlist the keywords to act on. So if you enter &lt;code&gt;gi&lt;/code&gt; followed by a space, it will execute (shown) the tooltip information. It then displays the information as given in the template. I customized the tooltip for git like this&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F700%2F1%2Ak7bL8fGALfRLqtwacmtexA.png" alt="" width="700" height="32"&gt;&lt;p&gt;Final Configuration&lt;/p&gt;
&lt;p&gt;For everybody who will use a copy past friendly version, you can use this gist&lt;/p&gt;&amp;lt;!--kg-card-begin: html--&amp;gt;&amp;lt;!--kg-card-end: html--&amp;gt;

</description>
      <category>powershell</category>
      <category>windows</category>
      <category>productivity</category>
      <category>terminal</category>
    </item>
    <item>
      <title>How to Build an Android App in Azure DevOps</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:59:21 +0000</pubDate>
      <link>https://dev.to/saschadev/how-to-build-an-android-app-in-azure-devops-f3d</link>
      <guid>https://dev.to/saschadev/how-to-build-an-android-app-in-azure-devops-f3d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/how-to-build-an-android-app-in-azure-devops/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/how-to-build-an-android-app-in-azure-devops/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you start to build an Android App in Xamarin, it is very handy to generate an App File for distribution, but sometimes the delivery time will get faster and you must publish very often new app versions with new features. &lt;/p&gt;
&lt;p&gt;Azure DevOps will support you in this case. Let me introduce you, to how we can generate a signed app automatically in Azure DevOps&lt;/p&gt;
&lt;h1 id="requirements"&gt;Requirements&lt;/h1&gt;
&lt;p&gt;When you create an APK-Package for the Android world, you will notice that the apk Files must be signed. For this, you must generate a Keystore file. If you have installed Visual Studio on your system with the Xamarin Development environment, you will get this tool installed. So open up a PowerShell Terminal end execute the following command &lt;/p&gt;
&lt;p&gt;After you will execute this, you will be prompted by several questions about the publisher like name and so on. But be careful, the parameter alias name will be required later in our pipeline and please be careful about the validity parameter. This will set the days when the Keystore expires, so when this happens, you will be not able to update the app in the google play store. &lt;/p&gt;
&lt;p&gt;So here is my output after I execute the command in my shell &lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-19.png" alt="" width="800" height="324"&gt;&lt;blockquote&gt;If you wonder how I get this nice command line, look at &lt;a href="blog.bajonczak.com/how-i-pimp-my-shell/"&gt;this post&lt;/a&gt;.&lt;/blockquote&gt;
&lt;p&gt;Now you have a generated Keystore, that you can use to sign the APK-Files.&lt;/p&gt;
&lt;h1 id="store-the-keystore-in-azure-devops"&gt;Store the Keystore in Azure DevOps&lt;/h1&gt;
&lt;p&gt;Azure DevOps contains a space that store files securely. So that nobody has access to it, except the users/pipelines that are granted. &lt;/p&gt;
&lt;p&gt;To store goto Pipelines-&amp;gt;Secure files-&amp;gt; + Secure file. You will get then a dialog like this (I already added the files to the upload section)&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-20.png" alt="" width="800" height="378"&gt;&lt;p&gt;After you click OK the file is stored. You can then configure the security (who has access to it) and so on. &lt;/p&gt;
&lt;p&gt;Next, we must set a variable group&lt;/p&gt;
&lt;h1 id="setup-the-variables"&gt;Setup the variables&lt;/h1&gt;
&lt;p&gt;A variable group is a container for several variables. It contains visible variables and secret variables (hidden by stars). &lt;/p&gt;
&lt;p&gt;I added a variable group called &lt;strong&gt;andriodApp&lt;/strong&gt;. In this group, I added the variables &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;KeystoreFile&lt;/li&gt;
&lt;li&gt;KeyStorePassword&lt;/li&gt;
&lt;li&gt;KeyStoreAlias&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2022%2F12%2Fimage-23.png" alt="" width="800" height="544"&gt;&lt;p&gt;The variable KeystoreFile is the name of the secure File, that we uploaded before. The KeystorePassword variable contains the associated password to the KeyStore. The alias name, that we defined before, ist stored in the KeyStoreAlias variable.&lt;/p&gt;
&lt;p&gt;Now that we have set the common variables that we can use, we can create our pipeline for building the apk file. &lt;/p&gt;
&lt;h1 id="create-the-pipeline"&gt;Create the Pipeline&lt;/h1&gt;
&lt;p&gt;I will skip the part to generate an empty YAML Pipeline because this is very intuitive and there are several howtos on the net that describe this better than me ;). &lt;/p&gt;
&lt;p&gt;So I use the following pipeline:&lt;/p&gt;
&lt;p&gt;You see that at the top we define on which branch the pipeline will trigger and which variable group it must use. Next to it there comes up the deployment steps. These will download the Keystore file we uploaded before. After restoring the required NuGet packages, it will compile the solution and generate the apk package. &lt;/p&gt;
&lt;p&gt;Now the important part, the next step will sign the apk package against the Keystore. &amp;nbsp;The rest is only the upload to the artifact store and nothing more. After all, these Packages is ready to use. &lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;With this simple HowTo, you are able to create a pipeline that will compile and deploy an android APK-File. In my other post about generating an ios Package you will see, that is not very different from that process. Sure Xamarin is not very popular, but it helps out in some cases. &lt;/p&gt;

</description>
      <category>azure</category>
      <category>devops</category>
      <category>android</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Deploy Sharepoint solutions without downtime... Really!</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 13:59:15 +0000</pubDate>
      <link>https://dev.to/saschadev/deploy-sharepoint-solutions-without-downtime-really-2o4c</link>
      <guid>https://dev.to/saschadev/deploy-sharepoint-solutions-without-downtime-really-2o4c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/deploy-sharepoint-solutions-without-downtime-really/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/deploy-sharepoint-solutions-without-downtime-really/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello my fellows, today I think it's time to get very serious about one MS product. Somehow will like it but most developer will hate it... SharePoint On-Premise. &lt;/p&gt;
&lt;p&gt;Each developer has it's own preference to deploy the solution. Maybe complete manually with some click adventures, or fully automated with a devops Pipeline. But everyone know, if you deploy a solution, the IIS Server gets restartet, there is now way to go around this. So it seems that there will be no zero Downtime solution possible. But that's wrong. Let me explain how you can archive this.&lt;/p&gt;
&lt;h1 id="requirements"&gt;Requirements&lt;/h1&gt;
&lt;p&gt;To make it clear, you cannot do a zero downtime deployment in a single server environment. You must have at least two Webfrontend (WFE) Servers. Usually, you have a load balancer configured o make a round robbin distribution of the request so that the load will be divided across all WFE-Servers. &lt;/p&gt;
&lt;p&gt;For example, let's assume you have the following simple server structure&lt;/p&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpub-3d6b5be904ac42e19eb06b614618c42e.r2.dev%2Fimages%2F2023%2F01%2Fimage.png" alt="" width="751" height="629"&gt;&lt;p&gt;When a user now accesses the server, the LB checks if the WFEs are available. If true, then it will send the user's request to the first WFE, and the next request will be sent to the second WFE, the next to the first WFE ..... This procedure explains the round robbin procedure. The important part here is that the LB every time checks if the WFE is available. &lt;/p&gt;
&lt;p&gt;So our requirements are &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minimum 2 WFE's&lt;/li&gt;
&lt;li&gt;A LB with availability checks &lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="the-basic-deployment"&gt;The basic deployment&lt;/h1&gt;
&lt;p&gt;Basically, a deployment of a SharePoint solution will be done like this&lt;/p&gt;
&lt;p&gt;Now the process starts, it will add some assembly to the GAC, and after this, it will send some data to the layouts folder and many more things. After that, the IIS pool will be recycled to get fresh assemblies. These processes will be done across all servers on the farm simultaneously.&lt;/p&gt;
&lt;p&gt;The consequence is that every user will be kicked out from the server and maybe get frustrated because he did some work and it could not be saved in SharePoint.&lt;/p&gt;

&lt;h1 id="modified-command"&gt;Modified command&lt;/h1&gt;
&lt;p&gt;Now lets configure the command a little bit&lt;/p&gt;
&lt;p&gt;When you execute this command, you will notice that the server (where you executed it) getting down, but the other server will be up. Why?&lt;/p&gt;
&lt;p&gt;with the parameter -Local we tell SharePoint to install the new Solution only on the current server only, now you can controll the behaviour of the deployment process. Now the availability checks are important.&lt;/p&gt;
&lt;p&gt;So in your example with two WFE you will execute on the first Server the update command with the local parameter. The Server getting down, the LB will notice this and redirecting all traffic to the second WFE. After the installation the server is up again and you can install the package on the seconde WFE. The LB will notice that the second will not be available anymore and redirects the users to the first server. So the user will not notice anything about a downtime because he will everytime gets redirected to the running server. &lt;/p&gt;
&lt;p&gt;To warmup the SharePoint and get a faster response you will execute a GET-Request to the server and imitiate a User request to the WFE. After this the server will be reqdy for a fast response.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Now you are able to deply Solution to your farm without any downtime. Because you have a well configured Loadbalancer that will switch automatically between the instances. So your &amp;nbsp;Deployment will concentrate on a single server, after the installation is complete, the next server will be updated. The loadbalancer then will switch between the available server. Sure in a Farm with many WFE Servers your can consider to deploy the solutions in groups. Try it out, you will see that it will get very handy, and support the DevOps lifecycle very much.&lt;/p&gt;


</description>
      <category>sharepoint</category>
      <category>devops</category>
      <category>microsoft365</category>
      <category>cicd</category>
    </item>
    <item>
      <title>What is Microsoft 365 eSignature?</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Fri, 10 Jul 2026 06:35:01 +0000</pubDate>
      <link>https://dev.to/saschadev/what-is-microsoft-365-esignature-1hf4</link>
      <guid>https://dev.to/saschadev/what-is-microsoft-365-esignature-1hf4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/what-is-microsoft-365-esignature/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/what-is-microsoft-365-esignature/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft 365 eSignature is Microsoft’s native way to request electronic signatures from documents stored in Microsoft 365, mainly SharePoint.&lt;/p&gt;

&lt;p&gt;That is the simple description. The more useful one is this: it is a signing workflow built into the place where many companies already keep their working documents. A user can start a request from SharePoint or, for supported Word scenarios, from Word. The recipients sign a PDF copy. The completed PDF lands back in SharePoint.&lt;/p&gt;

&lt;p&gt;For many teams, that is enough. For some teams, it is absolutely not enough. The trick is knowing the difference before people start using it for everything.&lt;/p&gt;

&lt;p&gt;If you are planning a rollout, use the full &lt;a href="https://dev.to/microsoft-365-esignature-guide/"&gt;Microsoft 365 eSignature guide&lt;/a&gt; and the &lt;a href="https://dev.to/microsoft-365-esignature-tenant-readiness-checklist/"&gt;tenant readiness checklist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens in the PDF flow
&lt;/h2&gt;

&lt;p&gt;A user opens an unencrypted PDF from a SharePoint document library, chooses the signing option, adds recipients, places signature fields and sends the request. Recipients get a notification, open the request, accept the electronic signing terms and sign.&lt;/p&gt;

&lt;p&gt;When the request is complete, SharePoint stores the signed PDF in the same location as the original file.&lt;/p&gt;

&lt;p&gt;That storage detail matters. It means the signed document can follow the same ownership, retention and audit thinking as the rest of the library, assuming your SharePoint governance is not already a mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens in Word
&lt;/h2&gt;

&lt;p&gt;Microsoft also supports eSignature from Word desktop for eligible users and documents. The Word file must be a &lt;code&gt;.docx&lt;/code&gt;, unencrypted and stored in a SharePoint site enabled for eSignature. Users can place eSignature fields directly in Word, then send a request.&lt;/p&gt;

&lt;p&gt;The signer does not edit the source Word file. They sign a generated PDF copy, and the completed PDF is saved back to the SharePoint location.&lt;/p&gt;

&lt;p&gt;This is useful for repeatable templates such as HR letters, approvals or standard agreements. It is not a full contract lifecycle system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kind of signature is this?
&lt;/h2&gt;

&lt;p&gt;Microsoft describes the service as using simple electronic signatures.&lt;/p&gt;

&lt;p&gt;That sentence should make legal and compliance teams pay attention. Simple electronic signatures are perfectly fine for many business processes, but they are not the answer to every regulated, high-risk or identity-sensitive scenario.&lt;/p&gt;

&lt;p&gt;Before rollout, decide which document types are allowed and which must stay with a stronger or more specialized signing platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why admins should care
&lt;/h2&gt;

&lt;p&gt;This feature leans heavily on SharePoint and Microsoft Entra behavior.&lt;/p&gt;

&lt;p&gt;The sender needs edit and sharing rights. External recipients may require Microsoft Entra B2B integration for SharePoint and OneDrive. Site sharing, unique permissions, sensitivity labels, conditional access, encryption and download restrictions can all affect whether the process works.&lt;/p&gt;

&lt;p&gt;So yes, it is an eSignature feature. But operationally it behaves like a SharePoint governance feature. The &lt;a href="https://dev.to/sharepoint-esignature-setup-governance/"&gt;SharePoint eSignature governance guide&lt;/a&gt; goes deeper on that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit trail
&lt;/h2&gt;

&lt;p&gt;Microsoft says eSignature activities can be searched in Microsoft Purview Audit. Useful events include request created, sent, canceled, declined, expired and completed. Recipient actions such as viewing, signing and downloading the signed document can also appear.&lt;/p&gt;

&lt;p&gt;For Microsoft 365-heavy organizations, that is one of the better arguments for the native option.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it is a good fit
&lt;/h2&gt;

&lt;p&gt;Microsoft 365 eSignature is worth considering when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the document already lives in SharePoint;&lt;/li&gt;
&lt;li&gt;the signing flow is simple;&lt;/li&gt;
&lt;li&gt;simple electronic signatures are legally sufficient;&lt;/li&gt;
&lt;li&gt;up to 10 recipients per request is enough;&lt;/li&gt;
&lt;li&gt;the signed copy should stay in Microsoft 365;&lt;/li&gt;
&lt;li&gt;the organization wants Purview audit visibility;&lt;/li&gt;
&lt;li&gt;buying another signing platform would be overkill.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When I would be careful
&lt;/h2&gt;

&lt;p&gt;I would be careful if the process needs advanced identity checks, heavy templates, complex routing, bulk sending, CRM integration, legal negotiation workflows or strict regulated signing requirements.&lt;/p&gt;

&lt;p&gt;In those cases, compare it properly against Adobe Acrobat Sign and DocuSign instead of forcing every use case into the Microsoft-native tool. I covered that in &lt;a href="https://dev.to/microsoft-365-esignature-vs-adobe-sign-vs-docusign/"&gt;Microsoft 365 eSignature vs Adobe Sign vs DocuSign&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Microsoft 365 eSignature is not magic and it is not a universal replacement for dedicated signing platforms.&lt;/p&gt;

&lt;p&gt;It is a pragmatic option for simple signing workflows that already belong in Microsoft 365. Used that way, it can reduce friction. Used without boundaries, it becomes one more thing admins have to clean up later.&lt;/p&gt;

</description>
      <category>microsoft365</category>
      <category>productivity</category>
      <category>security</category>
      <category>sharepoint</category>
    </item>
    <item>
      <title>Microsoft 365 eSignature Guide: Setup, Licensing, Governance and Troubleshooting</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Thu, 09 Jul 2026 17:10:06 +0000</pubDate>
      <link>https://dev.to/saschadev/microsoft-365-esignature-guide-setup-licensing-governance-and-troubleshooting-59nc</link>
      <guid>https://dev.to/saschadev/microsoft-365-esignature-guide-setup-licensing-governance-and-troubleshooting-59nc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog: &lt;a href="https://blog.bajonczak.com/microsoft-365-esignature-guide/" rel="noopener noreferrer"&gt;https://blog.bajonczak.com/microsoft-365-esignature-guide/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft 365 eSignature sounds like a small feature. Open a document, request a signature, store the signed PDF back in SharePoint. Done.&lt;/p&gt;

&lt;p&gt;In real tenants it is never quite that small.&lt;/p&gt;

&lt;p&gt;The moment you enable it, you touch billing, SharePoint sharing, external guests, document permissions, legal acceptance of electronic signatures, audit logs, sensitivity labels and support. That does not make the feature bad. It just means it needs to be rolled out like an admin feature, not like a nice new button in Office.&lt;/p&gt;

&lt;p&gt;This hub pulls the pieces together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The articles in this hub
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/what-is-microsoft-365-esignature/"&gt;What is Microsoft 365 eSignature?&lt;/a&gt; explains what the service actually does, where the signed document goes, and why the SharePoint integration matters.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/enable-microsoft-365-esignature-tenant/"&gt;How to enable Microsoft 365 eSignature in your tenant&lt;/a&gt; covers prerequisites, pay-as-you-go billing, the admin center setup and the checks I would run before a pilot.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/microsoft-365-esignature-licensing-pricing-limitations/"&gt;Microsoft 365 eSignature licensing, pricing and limitations&lt;/a&gt; looks at the request-based billing model, the current Microsoft-listed USD $2/request price and the limits that can surprise people.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/microsoft-365-esignature-vs-adobe-sign-vs-docusign/"&gt;Microsoft 365 eSignature vs Adobe Sign vs DocuSign&lt;/a&gt; helps decide when the Microsoft-native option is enough and when a dedicated signing platform is still the safer choice.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/sharepoint-esignature-setup-governance/"&gt;SharePoint eSignature setup and governance&lt;/a&gt; is the admin-focused part: sites, owners, sharing, labels, retention and audit.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/microsoft-365-esignature-troubleshooting/"&gt;Common Microsoft 365 eSignature problems and troubleshooting&lt;/a&gt; gives the service desk a starting point for missing buttons, failed external signers and permission problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Downloads for the rollout
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/microsoft-365-esignature-tenant-readiness-checklist/"&gt;Microsoft 365 eSignature Tenant Readiness Checklist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/microsoft-365-esignature-comparison-table/"&gt;Microsoft 365 eSignature Comparison Table&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/microsoft-365-esignature-governance-template/"&gt;Microsoft 365 eSignature Governance Template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I would use this hub
&lt;/h2&gt;

&lt;p&gt;If you are just evaluating the feature, start with the overview and pricing articles.&lt;/p&gt;

&lt;p&gt;If you are the admin who has to enable it, read the setup and governance articles first. The setup itself is not complicated. The messy part is deciding which sites are allowed to use it, who can send requests externally and what happens when a signer cannot access a document.&lt;/p&gt;

&lt;p&gt;If procurement or legal is asking whether this replaces Adobe Sign or DocuSign, do not answer with a simple yes or no. The honest answer is: sometimes. Microsoft 365 eSignature is a good fit for simple SharePoint-native signing flows. It is not automatically the right fit for advanced contract workflows, regulated signing, bulk send scenarios or heavy template automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;I like the direction Microsoft is taking here. A lot of companies already store the document in SharePoint, discuss it in Teams and audit it through Microsoft 365. For those companies, sending basic signing work out to another platform can feel unnecessary.&lt;/p&gt;

&lt;p&gt;But I would not enable this tenant-wide on a Friday afternoon.&lt;/p&gt;

&lt;p&gt;Start with a pilot. Pick a few sites with owners who actually care. Test internal and external signers. Watch the billing meter. Check the audit log. Break it on purpose with labels, permissions and conditional access. Then decide where it belongs.&lt;/p&gt;

&lt;p&gt;That is the difference between a useful Microsoft 365 feature and another uncontrolled workflow hiding in SharePoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;This guide is based on Microsoft Learn documentation for Microsoft 365 eSignature overview, setup, pay-as-you-go pricing, Word/PDF signing, Teams Approvals tracking and troubleshooting.&lt;/p&gt;

</description>
      <category>microsoft365</category>
      <category>productivity</category>
      <category>security</category>
      <category>governance</category>
    </item>
    <item>
      <title>Microsoft 365 eSignature vs Adobe Sign vs DocuSign</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Thu, 09 Jul 2026 11:29:14 +0000</pubDate>
      <link>https://dev.to/saschadev/microsoft-365-esignature-vs-adobe-sign-vs-docusign-4ob7</link>
      <guid>https://dev.to/saschadev/microsoft-365-esignature-vs-adobe-sign-vs-docusign-4ob7</guid>
      <description>&lt;p&gt;This is not a religious debate. It is a workflow decision.&lt;/p&gt;

&lt;p&gt;Microsoft 365 eSignature is attractive when the document already lives in SharePoint and the signing process is simple. Adobe Acrobat Sign and DocuSign are stronger when signing is part of a larger agreement process with templates, routing, identity options, integrations and reporting.&lt;/p&gt;

&lt;p&gt;This article belongs to the &lt;a href="https://dev.to/microsoft-365-esignature-guide/"&gt;Microsoft 365 eSignature guide&lt;/a&gt;. If you need a reusable decision artifact, download the &lt;a href="https://dev.to/microsoft-365-esignature-comparison-table/"&gt;comparison table&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Use Microsoft 365 eSignature when you want a simple Microsoft-native signing flow and the signed document should stay in SharePoint.&lt;/p&gt;

&lt;p&gt;Use Adobe Acrobat Sign when PDF-heavy workflows, Adobe tooling or mature signing templates are already part of the business.&lt;/p&gt;

&lt;p&gt;Use DocuSign when agreements are a serious business process rather than a document with a signature field.&lt;/p&gt;

&lt;p&gt;That is oversimplified, but it is a decent starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Microsoft 365 eSignature&lt;/th&gt;
&lt;th&gt;Adobe Acrobat Sign&lt;/th&gt;
&lt;th&gt;DocuSign&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Simple SharePoint-native signing&lt;/td&gt;
&lt;td&gt;PDF-heavy enterprise signing&lt;/td&gt;
&lt;td&gt;Advanced agreement workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage model&lt;/td&gt;
&lt;td&gt;Signed PDF returns to SharePoint&lt;/td&gt;
&lt;td&gt;Provider workflow with Microsoft integrations&lt;/td&gt;
&lt;td&gt;Provider workflow with Microsoft integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft 365 integration&lt;/td&gt;
&lt;td&gt;Native SharePoint, Word, Teams Approvals and Purview audit&lt;/td&gt;
&lt;td&gt;Strong Microsoft 365 integrations&lt;/td&gt;
&lt;td&gt;Strong Microsoft 365 integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing shape&lt;/td&gt;
&lt;td&gt;Pay-as-you-go per request&lt;/td&gt;
&lt;td&gt;Plan/licensing dependent&lt;/td&gt;
&lt;td&gt;Plan/licensing dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Governance model&lt;/td&gt;
&lt;td&gt;SharePoint and Entra governance matter heavily&lt;/td&gt;
&lt;td&gt;Provider admin plus integration governance&lt;/td&gt;
&lt;td&gt;Provider admin plus integration governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced templates&lt;/td&gt;
&lt;td&gt;Limited compared with dedicated platforms&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk send and routing&lt;/td&gt;
&lt;td&gt;Not the main strength&lt;/td&gt;
&lt;td&gt;Stronger&lt;/td&gt;
&lt;td&gt;Stronger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best admin argument&lt;/td&gt;
&lt;td&gt;Keeps simple workflows inside Microsoft 365&lt;/td&gt;
&lt;td&gt;Mature PDF signing ecosystem&lt;/td&gt;
&lt;td&gt;Mature agreement workflow ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where Microsoft 365 eSignature wins
&lt;/h2&gt;

&lt;p&gt;Microsoft wins on proximity.&lt;/p&gt;

&lt;p&gt;The document is already in SharePoint. The user is already in Microsoft 365. The signed PDF comes back to the same place. Audit activity can be searched in Microsoft Purview. Teams Approvals can help users track requests.&lt;/p&gt;

&lt;p&gt;For common internal approvals, HR forms, policy acknowledgements, procurement documents or low-complexity external signing, that is a strong story.&lt;/p&gt;

&lt;p&gt;It also reduces tool sprawl. If a process does not need a dedicated eSignature platform, keeping it inside Microsoft 365 is cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Adobe Sign and DocuSign win
&lt;/h2&gt;

&lt;p&gt;Dedicated platforms win when signing is not the whole process.&lt;/p&gt;

&lt;p&gt;Think reusable template libraries, conditional routing, bulk sending, advanced recipient authentication, branded experiences, CRM integration, contract operations, APIs, delegated administration and reporting. That is where Adobe Acrobat Sign and DocuSign have had years to mature.&lt;/p&gt;

&lt;p&gt;If sales, legal or procurement already run critical workflows through one of those platforms, do not rip it out just because Microsoft added a native option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance is different, not gone
&lt;/h2&gt;

&lt;p&gt;Microsoft 365 eSignature pushes governance into SharePoint and Entra. Site permissions, guest access, sharing restrictions, sensitivity labels and conditional access all matter.&lt;/p&gt;

&lt;p&gt;Adobe Sign and DocuSign push more of the workflow governance into the provider platform. You still need Microsoft integration governance, but the signing process itself is usually managed in the signing product.&lt;/p&gt;

&lt;p&gt;Neither model is automatically better. The better model is the one your organization can actually operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  My recommendation
&lt;/h2&gt;

&lt;p&gt;I would define three buckets:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Microsoft 365 eSignature for simple, SharePoint-native signing.&lt;/li&gt;
&lt;li&gt;Adobe Sign or DocuSign for advanced, regulated, high-volume or customer-critical workflows.&lt;/li&gt;
&lt;li&gt;No eSignature tool at all for things that should just be an approval, a form or a workflow task.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third bucket matters. Not every “please confirm this” needs a paid signature request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Microsoft 365 eSignature will not kill Adobe Sign or DocuSign. It does not need to.&lt;/p&gt;

&lt;p&gt;Its job is to cover the boring but useful middle: documents already in Microsoft 365 that need a simple signature and a signed PDF back in SharePoint. Use it there and it makes sense. Push it into every workflow and you will eventually regret it.&lt;/p&gt;

</description>
      <category>microsoft365</category>
      <category>productivity</category>
      <category>business</category>
      <category>security</category>
    </item>
    <item>
      <title>Common Microsoft 365 eSignature Problems and Troubleshooting</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Thu, 09 Jul 2026 11:29:01 +0000</pubDate>
      <link>https://dev.to/saschadev/common-microsoft-365-esignature-problems-and-troubleshooting-1ikn</link>
      <guid>https://dev.to/saschadev/common-microsoft-365-esignature-problems-and-troubleshooting-1ikn</guid>
      <description>&lt;p&gt;Most Microsoft 365 eSignature problems are not mysterious. They are usually SharePoint, permissions, external sharing, file format, labels or conditional access showing up as an eSignature issue.&lt;/p&gt;

&lt;p&gt;That is annoying, but it is also good news. You can troubleshoot it with a method instead of guessing.&lt;/p&gt;

&lt;p&gt;This article is part of the &lt;a href="https://dev.to/microsoft-365-esignature-guide/"&gt;Microsoft 365 eSignature guide&lt;/a&gt;. Keep it next to the &lt;a href="https://dev.to/microsoft-365-esignature-tenant-readiness-checklist/"&gt;tenant readiness checklist&lt;/a&gt; and the &lt;a href="https://dev.to/sharepoint-esignature-setup-governance/"&gt;SharePoint governance guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signature option is missing
&lt;/h2&gt;

&lt;p&gt;Start with the obvious checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is eSignature enabled in the Microsoft 365 admin center?&lt;/li&gt;
&lt;li&gt;Has it been less than 24 hours since activation?&lt;/li&gt;
&lt;li&gt;Is the document in a SharePoint site enabled for eSignature?&lt;/li&gt;
&lt;li&gt;Is the user opening the PDF from the SharePoint document library viewer?&lt;/li&gt;
&lt;li&gt;Did the user open the file in Edge, Adobe Reader or another viewer instead?&lt;/li&gt;
&lt;li&gt;Does the user have the right permissions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft’s troubleshooting guidance is clear on one point: the PDF viewer must be opened by selecting the PDF from a SharePoint library. If the user opens the file another way, the signing option may not appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user cannot create a request
&lt;/h2&gt;

&lt;p&gt;Check the document and the folder first.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the sender have edit rights?&lt;/li&gt;
&lt;li&gt;Does the sender have sharing rights to the folder?&lt;/li&gt;
&lt;li&gt;Is the PDF under 10 MB?&lt;/li&gt;
&lt;li&gt;Is the PDF unencrypted?&lt;/li&gt;
&lt;li&gt;Are site sharing settings restricted to owners?&lt;/li&gt;
&lt;li&gt;Is the document in a private group where the sender cannot share?&lt;/li&gt;
&lt;li&gt;Are DLP, sensitivity labels, encryption or access policies blocking the flow?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also check whether the document was previously signed. Microsoft says new eSignature requests cannot be started from documents that were already signed.&lt;/p&gt;

&lt;h2&gt;
  
  
  External recipient cannot sign
&lt;/h2&gt;

&lt;p&gt;External signing issues usually come down to guest access or conditional access.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is external sharing enabled at tenant and site level?&lt;/li&gt;
&lt;li&gt;If new external guests are allowed, is Microsoft Entra B2B integration for SharePoint and OneDrive configured?&lt;/li&gt;
&lt;li&gt;Are domain restrictions blocking the recipient?&lt;/li&gt;
&lt;li&gt;Is conditional access blocking the eSignature app or the guest flow?&lt;/li&gt;
&lt;li&gt;Did the signer receive the email?&lt;/li&gt;
&lt;li&gt;Did the message land in junk or spam?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft notes that admins may need to review conditional access behavior for the Microsoft eSign service. Be careful here. Do not punch a broad hole in conditional access just to make one document work. Test the policy change with security involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adobe Sign or DocuSign request fails from SharePoint
&lt;/h2&gt;

&lt;p&gt;When you start a request for another provider from SharePoint, the provider may need to open or download the document. Encryption, download restrictions and access policies can block that.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the document encrypted?&lt;/li&gt;
&lt;li&gt;Is download blocked?&lt;/li&gt;
&lt;li&gt;Does the sender have permission to download the file?&lt;/li&gt;
&lt;li&gt;Is the provider integration authorized correctly?&lt;/li&gt;
&lt;li&gt;Does the provider support the document state you are sending?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If users keep hitting this, revisit the tool decision. The &lt;a href="https://dev.to/microsoft-365-esignature-vs-adobe-sign-vs-docusign/"&gt;Microsoft 365 eSignature vs Adobe Sign vs DocuSign&lt;/a&gt; comparison can help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signed document cannot be accessed
&lt;/h2&gt;

&lt;p&gt;Microsoft checks whether the sender can write to the original location before and after signing. If permissions change while the request is in progress, the completed document can become hard to access.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the sender still have access to the originating folder?&lt;/li&gt;
&lt;li&gt;Was the file moved during the signing process?&lt;/li&gt;
&lt;li&gt;Did someone change library or folder permissions?&lt;/li&gt;
&lt;li&gt;Is the completion email offering temporary access?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is another reason to avoid running signature workflows from chaotic libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick PowerShell check for site sharing
&lt;/h2&gt;

&lt;p&gt;Microsoft’s troubleshooting article suggests checking SharePoint site sharing capabilities with SharePoint Online PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Connect-SPOService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://yourtenant-admin.sharepoint.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-SPOSite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Limit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;All&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SharingCapability&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is only a starting point. Tenant and site settings can look fine while folder permissions, labels or conditional access still block the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  My triage questions
&lt;/h2&gt;

&lt;p&gt;When someone reports an issue, ask these first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which SharePoint site and library contains the document?&lt;/li&gt;
&lt;li&gt;PDF or Word?&lt;/li&gt;
&lt;li&gt;Is the file encrypted or sensitivity-labeled?&lt;/li&gt;
&lt;li&gt;Internal signer or external signer?&lt;/li&gt;
&lt;li&gt;Existing guest or new external recipient?&lt;/li&gt;
&lt;li&gt;Does the sender have sharing rights?&lt;/li&gt;
&lt;li&gt;Can the issue be reproduced with a clean test document?&lt;/li&gt;
&lt;li&gt;Do Purview Audit events show anything useful?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That list will solve more cases than a long call full of screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Troubleshooting gets much easier when the rollout is designed properly. Approved sites, clear sharing rules, tested labels and a support path remove most of the chaos.&lt;/p&gt;

&lt;p&gt;If the tenant is already messy, Microsoft 365 eSignature will not create the mess. It will reveal it.&lt;/p&gt;

</description>
      <category>microsoft365</category>
      <category>sharepoint</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
    <item>
      <title>SharePoint eSignature Setup and Governance</title>
      <dc:creator>Der Sascha</dc:creator>
      <pubDate>Thu, 09 Jul 2026 11:28:42 +0000</pubDate>
      <link>https://dev.to/saschadev/sharepoint-esignature-setup-and-governance-db</link>
      <guid>https://dev.to/saschadev/sharepoint-esignature-setup-and-governance-db</guid>
      <description>&lt;p&gt;Microsoft 365 eSignature looks like a signing feature. Admins should treat it as SharePoint governance.&lt;/p&gt;

&lt;p&gt;The signing flow depends on the boring stuff: site permissions, sharing settings, guest access, labels, encryption, conditional access, ownership and retention. If those pieces are messy, eSignature will expose the mess very quickly.&lt;/p&gt;

&lt;p&gt;Use this article with the &lt;a href="https://dev.to/microsoft-365-esignature-governance-template/"&gt;Governance Template&lt;/a&gt; and the full &lt;a href="https://dev.to/microsoft-365-esignature-guide/"&gt;Microsoft 365 eSignature guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not start tenant-wide
&lt;/h2&gt;

&lt;p&gt;The first governance decision is scope.&lt;/p&gt;

&lt;p&gt;I would not enable every SharePoint site on day one. Start with a pilot group of sites where the business owner is known, the document types are clear and the permissions are not a museum of old projects and broken inheritance.&lt;/p&gt;

&lt;p&gt;Put sites into buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approved for eSignature;&lt;/li&gt;
&lt;li&gt;pilot only;&lt;/li&gt;
&lt;li&gt;blocked until ownership is fixed;&lt;/li&gt;
&lt;li&gt;requires legal or compliance review first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a site has no owner, it should not send signature requests. That rule alone prevents a lot of pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide who can send requests
&lt;/h2&gt;

&lt;p&gt;The sender needs edit and sharing rights. In many SharePoint sites, that describes far more people than admins realize.&lt;/p&gt;

&lt;p&gt;Define the model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can all site members send requests?&lt;/li&gt;
&lt;li&gt;Should only site owners or a dedicated group send them?&lt;/li&gt;
&lt;li&gt;Are external requests limited to certain teams?&lt;/li&gt;
&lt;li&gt;Are some libraries excluded?&lt;/li&gt;
&lt;li&gt;Does legal need to approve certain document types first?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is “we will see how people use it”, you are already choosing cleanup work later.&lt;/p&gt;

&lt;h2&gt;
  
  
  External signing needs real rules
&lt;/h2&gt;

&lt;p&gt;External signing is useful. It is also where many problems start.&lt;/p&gt;

&lt;p&gt;Microsoft notes that external recipients who are not existing guests require Microsoft Entra B2B integration for SharePoint and OneDrive plus guest sharing. That means your guest access and sharing posture are part of the eSignature design.&lt;/p&gt;

&lt;p&gt;Decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;existing guests only or new guests allowed;&lt;/li&gt;
&lt;li&gt;allowed and blocked domains;&lt;/li&gt;
&lt;li&gt;conditional access behavior;&lt;/li&gt;
&lt;li&gt;guest lifecycle and access reviews;&lt;/li&gt;
&lt;li&gt;who supports external signers;&lt;/li&gt;
&lt;li&gt;which document types may be sent outside the organization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not let every site invent its own answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test sensitivity labels before users do
&lt;/h2&gt;

&lt;p&gt;Sensitivity labels and encryption are exactly the kind of thing that look fine in a policy document and then break a workflow at the worst time.&lt;/p&gt;

&lt;p&gt;Test the labels your users actually apply. Test encrypted documents. Test restricted download policies. Test documents in libraries with unique permissions.&lt;/p&gt;

&lt;p&gt;If a label blocks signing, that may be correct. The point is to know before someone is trying to get a contract signed at 16:55.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan where signed PDFs live
&lt;/h2&gt;

&lt;p&gt;The completed signed PDF is stored back in SharePoint. Good. But which SharePoint location is the record?&lt;/p&gt;

&lt;p&gt;Decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether signed PDFs are records;&lt;/li&gt;
&lt;li&gt;how long they are retained;&lt;/li&gt;
&lt;li&gt;whether users can move or delete them;&lt;/li&gt;
&lt;li&gt;whether metadata is required;&lt;/li&gt;
&lt;li&gt;whether source documents and signed PDFs follow the same retention;&lt;/li&gt;
&lt;li&gt;who owns cleanup when a department reorganizes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not glamorous work. It is exactly the work that makes the feature safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Purview audit
&lt;/h2&gt;

&lt;p&gt;Microsoft says eSignature events can be searched in Microsoft Purview Audit. Track events such as request created, sent, canceled, declined, expired, completed, viewed, signed and downloaded.&lt;/p&gt;

&lt;p&gt;At minimum, admins should know how to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who sent this request?&lt;/li&gt;
&lt;li&gt;Who signed it?&lt;/li&gt;
&lt;li&gt;When was it completed?&lt;/li&gt;
&lt;li&gt;Was the signed document downloaded?&lt;/li&gt;
&lt;li&gt;Did the request fail or expire?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If nobody can answer those questions, the rollout is not ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare support before launch
&lt;/h2&gt;

&lt;p&gt;The common tickets will be predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing signature option;&lt;/li&gt;
&lt;li&gt;PDF too large or encrypted;&lt;/li&gt;
&lt;li&gt;user opened the PDF outside SharePoint;&lt;/li&gt;
&lt;li&gt;sender lacks sharing rights;&lt;/li&gt;
&lt;li&gt;external signer blocked by guest or conditional access settings;&lt;/li&gt;
&lt;li&gt;signed document cannot be saved because permissions changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Point support teams to the &lt;a href="https://dev.to/microsoft-365-esignature-troubleshooting/"&gt;troubleshooting guide&lt;/a&gt; and give them a test site where they can reproduce issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  My baseline policy
&lt;/h2&gt;

&lt;p&gt;Enable eSignature only for sites with an owner, a defined document scope, tested external sharing behavior, known retention expectations and a support path.&lt;/p&gt;

&lt;p&gt;If that feels strict, good. A signature workflow creates business evidence. It should not live in an ownerless library with mystery permissions.&lt;/p&gt;

</description>
      <category>microsoft365</category>
      <category>sharepoint</category>
      <category>governance</category>
      <category>security</category>
    </item>
  </channel>
</rss>
