Replacing Gmail’s POP3 import with go-imapproc

For years, I have been using Gmail’s “Check mail from other accounts” feature to pull in e-mails from my GMX address and my personal mail server into my Gmail inbox. Simple, reliable, and totally invisible once set up. In the times of SPF, DMARC, and what-not-else, simply forwarding e-mails to another address is just not really reliable any more.

Then came the e-mail from Google. Google is deprecating the POP3 import feature in Gmail. New users can no longer set it up as of early 2026, and existing users will lose it later in the year. The suggested alternatives are either to configure automatic forwarding on the source account (which requires the source provider to support it and, as mentioned, does not work too reliably due to various anti-SPAM techniques) or to add the account in the Gmail mobile app – which helps exactly nobody who reads their e-mail on a desktop. Also it doesn’t give you the “search everything in one place” benefit.

So I needed a replacement. The idea was to connect to IMAP and use Gmails Import-API to get the mail into Gmail. So I wrote go-imapproc, a small Go daemon that connects to an IMAP mailbox and processes each unread message by piping it to an external program. When the program exits successfully, the message is marked as read (or deleted, or moved).

The “external program” in my case is a Python script bundled with the project (gws-import-to-gmail-direct.py) that takes the raw e-mail from stdin and imports it directly into Gmail via the Gmail REST API, using OAuth2 credentials managed by the Google Workspace CLI. This applies all the standard Google Spam filtering, which is quite good.

An additional upside: instead of polling on a timer, go-imapproc uses IMAP IDLE. The connection stays open and the server pushes a notification the moment a new message arrives. In practice, mail now shows up in my Gmail inbox within seconds of being delivered to my other inboxes – not within the next hour as it did due to Gmails 60 minute poll interval.

go-imapproc is open source and available on GitHub under the MIT license. The project includes the helper scripts for Gmail import, a built-in health endpoint, and a Docker setup if you prefer to run it containerized (as I always do).

Disclaimer: I used this project as an opportunity to gain more hands-on experience with AI-assisted development. The code-base is not “vibe coded” in the dismissive sense of the term, though. Significant effort went into unit and integration tests, following sound engineering principles, and making sure I actually understood and stood behind every piece of code that ended up in the repository. The AI was a tool, not a substitute for engineering judgment. All commits made by AI agents are clearly marked as such in the commit history.

The project has now been importing all my personal mails from several source-accounts for about 2-3 months now, all without any issues. I hope it will be usable to others as well.

P.S.: Importing to Gmail is just one use-case. There is a lot more you can script based on processing new incoming e-mails when they arrive.

Helper bash scripts to move projects from your small SSD to a larger HDD (and back)

I use a Dell 9020m Micro-PC as “terminal-server” for development. It has a small SSD, but also a larger HDD attached.

I’ve created two simple bash-scripts that help move projects I currently don’t work on from SSD to HDD. They add symlink to the “off-boarded” location so that I can basically keep on working on them, but with slower I/O. When I want to work on the project again (and need the I/O speed), the project is moved back to the SSD (“on-boarding”).

To make re-off-boarding fast, the on-boarded state is kept on the HDD and re-used when off-boarding the project again.

These two simple scripts have been released under MIT license on GitHub.

Add SSH host key fingerprint to Jenkins for Git checkouts

I have a self-hosted Gitea instance, and also operate my own Jenkins instance. On the Jenkins instance, strict host-key checking is enabled. When adding the first reference to a Git repository hosted on my server, the following error appears:

Failed to connect to repository : Command "git ls-remote -h -- ssh://git@<myserver>:22222/martin/jenkins-test-docker-pipeline.git HEAD" returned status code 128:
stdout:
stderr: No ECDSA host key is known for [myserver]:22222 and you have requested strict checking.
Host key verification failed.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

The reason is that since it’s the first time I’m accessing a repository on this server, so the SSH host fingerprint is not in the known_hosts file for this SSH connection. Since I run this installation of Jenkins inside a Docker container and I don’t want to manually edit files in the file-system, I rely on setting the appropriate settings in Manage Jenkins > Security > Git Host Key Verification Configuration. This is set to Manually Provided Keys.

The easy solution is to set it to Accept First Connection. But I want to be stay on the manual mode. The easiest way to get the SSH host fingerprint via ssh-keyscan (-p 2222 is for specifying the SSH server port, which is a non-standard port in my case):

ssh-keyscan -p 22222 myserver

The output looks like this:

# myserver:22222 SSH-2.0-OpenSSH_9.1
[myserver]:22222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC085ixMnTlpr0pxXmkeJ6X479mbW/9PGDeUvD8hnG7EVUn3WsnnSG8yZkmU+jzg2W+xmFd7WIdaYLt6UcGvCS3RZIye68+qu64UToKX6CdTQOWyj6z9kd8tLoPBobsBd7tRyGaXU4c4UkCR5M44KhYtbQz0bgL7u+sL0z+R3lbOVyXaYPiSmUf/Wsd8fA2VcdWHkXJx0MMNMSVj/hgkZR7RfHzP4SZSqRLhn/AzIdx4DDuyGyPbVxu1ppnFtumRwlBkgat9UpMWkelREhcUdJtrZO1KPpA6DOkxIH8X/WtXyWToS9EjPb8FVTvzdjG2C4Zi0DkogH3no9vQcXLiihz
# myserver:22222 SSH-2.0-OpenSSH_9.1
[myserver]:22222 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDfTT9eEpDmd7ToGAorTW1X9uuJVhZl+KX9phmTpTy2e8U7l31jWn2TnKlXOp5oKgivpQ2cVjcTyazyrFB7MhgI=
# myserver:22222 SSH-2.0-OpenSSH_9.1
[myserver]:22222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFoEzPpEWApszceLM/jWHvAbrTppjsTzftw79yTSS5Po
# myserver:22222 SSH-2.0-OpenSSH_9.1
# myserver:22222 SSH-2.0-OpenSSH_9.1

It only makes sense to copy the non-comment lines (the ones not starting with a # to the configuration).

Now Git checkouts to this repository should work, once you have configured the appropriate credentials.

cygwin & Play Framework / Typesafe Activator

I am currently playing around with using Play Framework on cygwin. I noticed that Typesafe Activator, which has replaced the play command in recent Play versions, ruins the mintty terminal of cygwin: you won’t see any echo of your keystrokes after activator returns.

A simple solution is to blindly type stty sane into the terminal, which will reset it. Another way is, to wrap the activator in a special cygwin-activator bash script:

#!/bin/bash
activator $@
R=$?
stty sane
exit $R

The power of git aliases

Based on a recent question on stackoverflow I found the power of git aliases and want so share one I invented for answering the question and after that I found very useful in everyday git use:

git config --global alias.add-commit '!git add -A && git commit'

After this, you can simply check in all new, modified, and deleted files with a simple

git add-commit -m 'My commit message'

I have aliased this command also to git ac in order to save further on typing. I never thought that this combination could be that useful, but actually I think it really is. Thanks to the questioner for bringing the idea up.

How to force Git to consider a file as binary

If you are using Git on Windows and follow my advise on how to get past the problem with the “suspicious patch lines”, you might run into problems if you are using Encapsulated PostScript (.eps) files in your repository.

PostScript files are almost plain-text files, and if you set core.autocrlf and core.safecrlf, they might cause problems with the EPS binary encoded parts, as they might be detected as text-files and therefore remove any CRLF and replace it with single LF, which can mess up the whole image.

To force Git to consider a file binary which it would consider as text-file otherwise, the easiest way is to add a .gitattributes file to the directory containing the file or to any parent directory. In my case, I normally add a .gitattributes file in the root of the repository, containing

*.eps -text -diff
*.jpg -text -diff
*.png -text -diff

In the file you set attributes to a path (or a pattern), or unset them (with the minus sign).  The text attribute is the attribute which tells that end-of-line normalization should be applied to a file. If you unset it, Git won’t mess with the line endings in the file and consider it binary.

More details can be found on the gitattributes man page.

My initial git settings for any repository

This is my cheat sheet for the settings I use for my git-repositories (list to be edited continuously):

Global settings:

git config –global user.name Martin Carpella
git config –global user.email xxx@yyy.invalid
git config –global color.ui auto

Per-repository settings:

git config core.autocrlf true
git config core.safecrlf true

Per-repository .gitignore for Visual Studio/C# projects:

bin
obj
*.user
*.suo
Thumbs.db

git-svn fails with fatal error: unable to remap

Git‘s nice Subversion (SVN) integration is one of the reasons I switched to using it within our company for my own revision control besides our official repository. Unfortunately, upgrading cygwin broke my system once again:

$ git svn dcommit
6 [main] perl 4760 C:\cygwin\bin\perl.exe: *** fatal error - unable to remap C:\cygwin\lib\perl5\site_perl\5.8\cygwin\auto\SVN\_Core\_ Core.dll to same address as parent(0x260000) != 0x990000 84 [main] perl 3224 fork: child 4760 - died waiting for dll loading, errno 11 panic: MUTEX_LOCK (45) [util.c:2331] at /usr/bin/git-svn line 787. panic: MUTEX_LOCK (45) [op.c:352].

The reason behind this behavior is a huge difference in the way processes and threads and libraries are created/handled on Windows and Linux. git-svn relies on perl within cygwin and several perl libraries that use the same base-address for libraries internally. Of course, no two libraries can be loaded to the same base-address at the same time.

Long explanation, short way to fix the problem:

  1. Quit all cygwin processes
  2. Start ash (<cygroot>\bin\ash.exe) (<edit>Use “Run as Administrator…”</edit>)
  3. Execute /usr/bin/rebaseall

Voilla, that’s all. git-svn should work again.

.NET strings are not always immutable!

Strings are immutable. If you want to modify a sequence of characters, use StringBuilder. At least, that’s whats officially said. But in the framework there is at least one method that does modify a string:

TextRenderer.MeasureText() with ModifyString and EndEllipses will modify your string to match the ellipsed text if ellipsing happens. You can look at this VB# example on codeproject using TextRenderer.MeasureText() for trimming text on how it is used.

The string seems to be modified directly in native code by DrawTextEx from user32.dll. Additionally to the scary fact that strings are not immutable, the length of the string is not updated, regardless if the resulting string is shorter!

For instance if you have a string “aaaaaaa” which will be truncated to “aa...“, the Length property will still return 7 for the shortened string. The debugger shows that the string will in fact be “aa…\0a” after the operation. So maybe it might be right that the string is still 7 characters long but most outputting functionality like Console.Out.WriteLine() gets confused sometimes and stops any further output to the debugger or console under certain conditions.

A very quick investigation of the System.Drawing assembly using Lutz Roeder’s fabulous .NET Reflector showed that at least there should be no memory corruption in case “WW” would get ellipsed to “W...“, as DrawTextEx takes the length of the buffer and should result only in “W.“.

Summing up, I find the corruption of an immutable string by an official Microsoft API very troubling.

Content-aware image resizing

Krispin made me aware of a very cool new technique for resizing images: content-aware image resizing. Based on an energy-function path of an image are removed when shrinking or are duplicated and interpolated when growing the image in a non-uniform way.

This technique can also be used to remove objects from a given image. There is a nice demo video available on YouTube (it’s the same as in jfo’s coding blog, where Krispin originally found the information):

[youtube vIFCV2spKtg]

(via jfo’s coding and slashdot)