diff --git a/src/core/attachmentmodel.cpp b/src/core/attachmentmodel.cpp
index 25ddaf9..80dfb62 100644
--- a/src/core/attachmentmodel.cpp
+++ b/src/core/attachmentmodel.cpp
@@ -1,349 +1,349 @@
 // SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com>
 // SPDX-FileCopyCopyright: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
 // SPDX-License-Identifier: LGPL-2.0-or-later
 
 #include "attachmentmodel.h"
 
 #include "mimetreeparser_core_debug.h"
 #include "objecttreeparser.h"
 
 #include <QGpgME/ImportJob>
 #include <QGpgME/Protocol>
 
 #include <KLocalizedString>
 #include <KMime/Content>
 
 #include <QDesktopServices>
 #include <QDir>
 #include <QFile>
 #include <QGuiApplication>
 #include <QIcon>
 #include <QMimeDatabase>
 #include <QMimeType>
 #include <QRegularExpression>
 #include <QStandardPaths>
-#include <QTemporaryFile>
+#include <QTemporaryDir>
 #include <QUrl>
 
 namespace
 {
 
 QString sizeHuman(float size)
 {
     QStringList list;
     list << QStringLiteral("KB") << QStringLiteral("MB") << QStringLiteral("GB") << QStringLiteral("TB");
 
     QStringListIterator i(list);
     QString unit = QStringLiteral("Bytes");
 
     while (size >= 1024.0 && i.hasNext()) {
         unit = i.next();
         size /= 1024.0;
     }
 
     if (unit == QStringLiteral("Bytes")) {
         return QString().setNum(size) + QStringLiteral(" ") + unit;
     } else {
         return QString().setNum(size, 'f', 2) + QStringLiteral(" ") + unit;
     }
 }
 
 // SPDX-SnippetBegin
 // Copyright (C) 2016 The Qt Company Ltd.
 // SPDX-License-Identifier: GPL-3.0-only
 
 #define WINDOWS_DEVICES_PATTERN "(CON|AUX|PRN|NUL|COM[1-9]|LPT[1-9])(\\..*)?"
 
 // Naming a file like a device name will break on Windows, even if it is
 // "com1.txt". Since we are cross-platform, we generally disallow such file
 //  names.
 const QRegularExpression &windowsDeviceNoSubDirPattern()
 {
     static const QRegularExpression rc(QStringLiteral("^" WINDOWS_DEVICES_PATTERN "$"), QRegularExpression::CaseInsensitiveOption);
     Q_ASSERT(rc.isValid());
     return rc;
 }
 
 const QRegularExpression &windowsDeviceSubDirPattern()
 {
     static const QRegularExpression rc(QStringLiteral("^.*[/\\\\]" WINDOWS_DEVICES_PATTERN "$"), QRegularExpression::CaseInsensitiveOption);
     Q_ASSERT(rc.isValid());
     return rc;
 }
 
 /* Validate a file base name, check for forbidden characters/strings. */
 
 #define SLASHES "/\\"
 
 static const char notAllowedCharsSubDir[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`' ";
 static const char notAllowedCharsNoSubDir[] = ",^@={}[]~!?:&*\"|#%<>$\"'();`' " SLASHES;
 
 static const char *notAllowedSubStrings[] = {".."};
 
 bool validateFileName(const QString &name, bool allowDirectories)
 {
     if (name.isEmpty()) {
         return false;
     }
 
     // Characters
     const char *notAllowedChars = allowDirectories ? notAllowedCharsSubDir : notAllowedCharsNoSubDir;
     for (const char *c = notAllowedChars; *c; c++) {
         if (name.contains(QLatin1Char(*c))) {
             return false;
         }
     }
 
     // Substrings
     const int notAllowedSubStringCount = sizeof(notAllowedSubStrings) / sizeof(const char *);
     for (int s = 0; s < notAllowedSubStringCount; s++) {
         const QLatin1String notAllowedSubString(notAllowedSubStrings[s]);
         if (name.contains(notAllowedSubString)) {
             return false;
         }
     }
 
     // Windows devices
     bool matchesWinDevice = name.contains(windowsDeviceNoSubDirPattern());
     if (!matchesWinDevice && allowDirectories) {
         matchesWinDevice = name.contains(windowsDeviceSubDirPattern());
     }
     return !matchesWinDevice;
 }
 // SPDX-SnippetEnd
 }
 
 class AttachmentModelPrivate
 {
 public:
     AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser);
 
     AttachmentModel *q;
     QMimeDatabase mimeDb;
     std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
     MimeTreeParser::MessagePart::List mAttachments;
 };
 
 AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser)
     : q(q_ptr)
     , mParser(parser)
 {
     mAttachments = mParser->collectAttachmentParts();
 }
 
 AttachmentModel::AttachmentModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser)
     : QAbstractTableModel()
     , d(std::unique_ptr<AttachmentModelPrivate>(new AttachmentModelPrivate(this, parser)))
 {
 }
 
 AttachmentModel::~AttachmentModel()
 {
 }
 
 QHash<int, QByteArray> AttachmentModel::roleNames() const
 {
     return {
         {TypeRole, QByteArrayLiteral("type")},
         {NameRole, QByteArrayLiteral("name")},
         {SizeRole, QByteArrayLiteral("size")},
         {IconRole, QByteArrayLiteral("iconName")},
         {IsEncryptedRole, QByteArrayLiteral("encrypted")},
         {IsSignedRole, QByteArrayLiteral("signed")},
     };
 }
 
 QVariant AttachmentModel::headerData(int section, Qt::Orientation orientation, int role) const
 {
     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
         switch (section) {
         case NameColumn:
             return i18ndc("mimetreeparser", "@title:column", "Name");
         case SizeColumn:
             return i18ndc("mimetreeparser", "@title:column", "Size");
         case IsEncryptedColumn:
             return i18ndc("mimetreeparser", "@title:column", "Encrypted");
         case IsSignedColumn:
             return i18ndc("mimetreeparser", "@title:column", "Signed");
         }
     }
     return {};
 }
 
 QVariant AttachmentModel::data(const QModelIndex &index, int role) const
 {
     const auto row = index.row();
     const auto column = index.column();
 
     const auto part = d->mAttachments.at(row);
     Q_ASSERT(part);
     auto node = part->node();
     if (!node) {
         qWarning() << "no content for attachment";
         return {};
     }
     const auto mimetype = d->mimeDb.mimeTypeForName(QString::fromLatin1(part->mimeType()));
     const auto content = node->encodedContent();
 
     switch (column) {
     case NameColumn:
         switch (role) {
         case TypeRole:
             return mimetype.name();
         case Qt::DisplayRole:
         case NameRole:
             return part->filename();
         case IconRole:
             return mimetype.iconName();
         case Qt::DecorationRole:
             return QIcon::fromTheme(mimetype.iconName());
         case SizeRole:
             return sizeHuman(content.size());
         case IsEncryptedRole:
             return part->encryptions().size() > 0;
         case IsSignedRole:
             return part->signatures().size() > 0;
         case AttachmentPartRole:
             return QVariant::fromValue(part);
         default:
             return {};
         }
     case SizeColumn:
         switch (role) {
         case Qt::DisplayRole:
             return sizeHuman(content.size());
         default:
             return {};
         }
     case IsEncryptedColumn:
         switch (role) {
         case Qt::CheckStateRole:
             return part->encryptions().size() > 0 ? Qt::Checked : Qt::Unchecked;
         default:
             return {};
         }
     case IsSignedColumn:
         switch (role) {
         case Qt::CheckStateRole:
             return part->signatures().size() > 0 ? Qt::Checked : Qt::Unchecked;
         default:
             return {};
         }
     default:
         return {};
     }
 }
 
 QString AttachmentModel::saveAttachmentToPath(const int row, const QString &path, bool readonly)
 {
     const auto part = d->mAttachments.at(row);
     return saveAttachmentToPath(part, path, readonly);
 }
 
 QString AttachmentModel::saveAttachmentToPath(const MimeTreeParser::MessagePart::Ptr &part, const QString &path, bool readonly)
 {
     Q_ASSERT(part);
     auto node = part->node();
     auto data = node->decodedContent();
     // This is necessary to store messages embedded messages (EncapsulatedRfc822MessagePart)
     if (data.isEmpty()) {
         data = node->encodedContent();
     }
     if (part->isText()) {
         // convert CRLF to LF before writing text attachments to disk
         data = KMime::CRLFtoLF(data);
     }
 
     QFile f(path);
     if (!f.open(QIODevice::ReadWrite)) {
         qCWarning(MIMETREEPARSER_CORE_LOG) << "Failed to write attachment to file:" << path << " Error: " << f.errorString();
         Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to save attachment."));
         return {};
     }
     f.write(data);
     if (readonly) {
         // make file read-only so that nobody gets the impression that he migh edit attached files
         f.setPermissions(QFileDevice::ReadUser);
     }
     f.close();
     qCInfo(MIMETREEPARSER_CORE_LOG) << "Wrote attachment to file: " << path;
     return path;
 }
 
 bool AttachmentModel::openAttachment(const int row)
 {
     const auto part = d->mAttachments.at(row);
     return openAttachment(part);
 }
 
 bool AttachmentModel::openAttachment(const MimeTreeParser::MessagePart::Ptr &message)
 {
-    const QString tempDir = QDir::tempPath() + QLatin1Char('/') + qGuiApp->applicationName();
     QString fileName = message->filename();
     QString errorMessage;
-    if (message->filename().isEmpty() || validateFileName(fileName, false)) {
-        QTemporaryFile file;
+    QTemporaryDir tempDir(QDir::tempPath() + QLatin1Char('/') + qGuiApp->applicationName() + QStringLiteral(".XXXXXX"));
+    // TODO: We need some cleanup here. Otherwise the files will stay forever on Windows.
+    tempDir.setAutoRemove(false);
+    if (message->filename().isEmpty() || !validateFileName(fileName, false)) {
         const auto mimetype = d->mimeDb.mimeTypeForName(QString::fromLatin1(message->mimeType()));
-        file.setFileTemplate(tempDir + QStringLiteral("XXXXXX.") + mimetype.preferredSuffix());
-        file.setAutoRemove(false);
-        if (!file.open()) {
-            Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to create temporary file."));
-            return false;
-        }
-        fileName = file.fileName();
+        fileName = tempDir.filePath(i18n("attachment") + QLatin1Char('.') + mimetype.preferredSuffix());
     } else {
-        fileName = tempDir + QLatin1Char('/') + message->filename();
+        fileName = tempDir.filePath(message->filename());
     }
 
     const auto filePath = saveAttachmentToPath(message, fileName, true);
-    if (!QDesktopServices::openUrl(QUrl(QStringLiteral("file://") + filePath))) {
+    if (filePath.isEmpty()) {
+        Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to write attachment for opening."));
+        return false;
+    }
+
+    if (!QDesktopServices::openUrl(QUrl::fromLocalFile(filePath))) {
         Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "Failed to open attachment."));
         return false;
     }
     return true;
 }
 
 bool AttachmentModel::importPublicKey(const int row)
 {
     const auto part = d->mAttachments.at(row);
     return importPublicKey(part);
 }
 
 bool AttachmentModel::importPublicKey(const MimeTreeParser::MessagePart::Ptr &part)
 {
     Q_ASSERT(part);
     const QByteArray certData = part->node()->decodedContent();
     QGpgME::ImportJob *importJob = QGpgME::openpgp()->importJob();
 
     connect(importJob, &QGpgME::AbstractImportJob::result, this, [this](const GpgME::ImportResult &result) {
         if (result.numConsidered() == 0) {
             Q_EMIT errorOccurred(i18ndc("mimetreeparser", "@info", "No keys were found in this attachment"));
             return;
         } else {
             QString message = i18ndcp("mimetreeparser", "@info", "one key imported", "%1 keys imported", result.numImported());
             if (result.numUnchanged() != 0) {
                 message += QStringLiteral("\n")
                     + i18ndcp("mimetreeparser", "@info", "one key was already imported", "%1 keys were already imported", result.numUnchanged());
             }
             Q_EMIT info(message);
         }
     });
     GpgME::Error err = importJob->start(certData);
     return !err;
 }
 
 int AttachmentModel::rowCount(const QModelIndex &parent) const
 {
     if (!parent.isValid()) {
         return d->mAttachments.size();
     }
     return 0;
 }
 
 int AttachmentModel::columnCount(const QModelIndex &parent) const
 {
     if (!parent.isValid()) {
         return ColumnCount;
     }
     return 0;
 }