diff --git a/README.md b/README.md
index d16e9c083..1855f8500 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,39 @@ Please search for any [existing issues](https://github.com/oxen-io/session-deskt
 
 Build instructions can be found in [BUILDING.md](BUILDING.md).
 
+
+## Verifing signatures
+
+
+Get Kee's key and import it:
+```
+wget https://raw.githubusercontent.com/oxen-io/oxen-core/master/utils/gpg_keys/KeeJef.asc
+gpg --import KeeJef.asc
+```
+
+Get the signed hash for this release, the SESSION_VERSION needs to be updated for the release you want to verify
+```
+export SESSION_VERSION=1.6.1
+wget https://github.com/oxen-io/session-desktop/releases/download/v$SESSION_VERSION/signatures.asc
+```
+
+Verify the signature of the hashes of the files
+
+```
+gpg --verify signatures.asc 2>&1 |grep "Good signature from"
+```
+
+The command above should print "`Good signature from "Kee Jefferys...`"
+If it does, the hashes are valid but we still have to make the sure the signed hashes matches the downloaded files.
+
+Make sure the two commands below returns the same hash.
+If they do, files are valid
+```
+sha256sum session-desktop-linux-amd64-$SESSION_VERSION.deb
+grep .deb signatures.asc
+```
+
+
 ## Debian repository
 
 Please visit https://deb.oxen.io/<br/>
diff --git a/background.html b/background.html
index 7d02dfdb6..58cf594e8 100644
--- a/background.html
+++ b/background.html
@@ -10,7 +10,7 @@
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <meta http-equiv="Content-Security-Policy" content="default-src 'none';
             child-src 'self';
-            connect-src 'self' https: wss:;
+            connect-src 'self' https: wss: blob:;
             font-src 'self';
             form-action 'self';
             frame-src 'none';
@@ -167,4 +167,4 @@
   <script type='text/javascript' src='js/background.js'></script>
 </body>
 
-</html>
\ No newline at end of file
+</html>
diff --git a/js/background.js b/js/background.js
index 04a23597d..00c0bb2b4 100644
--- a/js/background.js
+++ b/js/background.js
@@ -425,7 +425,7 @@
           avatarPath,
           onOk: async (newName, avatar) => {
             let newAvatarPath = '';
-            let url = null;
+            let fileUrl = null;
             let profileKey = null;
             if (avatar) {
               const data = await readFile({ file: avatar });
@@ -463,22 +463,18 @@
                   profileKey
                 );
 
-                const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatarV1({
-                  ...dataResized,
-                  data: encryptedData,
-                  size: encryptedData.byteLength,
-                });
+                const avatarPointer = await window.Fsv2.uploadFileToFsV2(encryptedData);
 
-                ({ url } = avatarPointer);
+                ({ fileUrl } = avatarPointer);
 
                 storage.put('profileKey', profileKey);
 
-                conversation.set('avatarPointer', url);
+                conversation.set('avatarPointer', fileUrl);
 
                 const upgraded = await Signal.Migrations.processNewAttachment({
                   isRaw: true,
                   data: data.data,
-                  url,
+                  url: fileUrl,
                 });
                 newAvatarPath = upgraded.path;
                 // Replace our temporary image with the attachment pointer from the server:
@@ -513,14 +509,6 @@
             // so we could disable this here
             // or least it enable for the quickest response
             window.lokiPublicChatAPI.setProfileName(newName);
-
-            if (avatar) {
-              window
-                .getConversationController()
-                .getConversations()
-                .filter(convo => convo.isPublic())
-                .forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey }));
-            }
           },
         });
       }
diff --git a/libtextsecure/crypto.d.ts b/libtextsecure/crypto.d.ts
index 441d59ee5..54b1d1b65 100644
--- a/libtextsecure/crypto.d.ts
+++ b/libtextsecure/crypto.d.ts
@@ -13,4 +13,5 @@ export interface LibTextsecureCryptoInterface {
     theirDigest: ArrayBuffer
   ): Promise<ArrayBuffer>;
   decryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer>;
+  encryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer>;
 }
diff --git a/package.json b/package.json
index 3faef1cf1..eb19f61bc 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "session-desktop",
   "productName": "Session",
   "description": "Private messaging from your desktop",
-  "version": "1.6.2",
+  "version": "1.6.4",
   "license": "GPL-3.0",
   "author": {
     "name": "Loki Project",
diff --git a/preload.js b/preload.js
index 31dd86729..bf1ce9ebb 100644
--- a/preload.js
+++ b/preload.js
@@ -398,6 +398,7 @@ window.addEventListener('contextmenu', e => {
 });
 
 window.NewReceiver = require('./ts/receiver/receiver');
+window.Fsv2 = require('./ts/fileserver/FileServerApiV2');
 window.DataMessageReceiver = require('./ts/receiver/dataMessage');
 window.NewSnodeAPI = require('./ts/session/snode_api/SNodeAPI');
 window.SnodePool = require('./ts/session/snode_api/snodePool');
diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx
index f2c385488..a39e0d47e 100644
--- a/ts/components/Avatar.tsx
+++ b/ts/components/Avatar.tsx
@@ -91,7 +91,11 @@ export const Avatar = (props: Props) => {
   // contentType is not important
   const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', '');
   const handleImageError = () => {
-    window?.log?.warn('Avatar: Image failed to load; failing over to placeholder', urlToLoad);
+    window.log.warn(
+      'Avatar: Image failed to load; failing over to placeholder',
+      urlToLoad,
+      avatarPath
+    );
     setImageBroken(true);
   };
 
diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index a2404c912..76f1ce05f 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -229,7 +229,7 @@ class ConversationListItem extends React.PureComponent<Props> {
     const displayName = isMe ? i18n('noteToSelf') : profileName;
 
     let shouldShowPubkey = false;
-    if (!name || name.length === 0) {
+    if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
       shouldShowPubkey = true;
     }
 
diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx
index c24bdf11f..497dde9df 100644
--- a/ts/components/session/ActionsPanel.tsx
+++ b/ts/components/session/ActionsPanel.tsx
@@ -7,11 +7,13 @@ import { ConversationController } from '../../session/conversations';
 import { UserUtils } from '../../session/utils';
 import { syncConfigurationIfNeeded } from '../../session/utils/syncUtils';
 import { DAYS, MINUTES } from '../../session/utils/Number';
+
 import {
+  createOrUpdateItem,
   generateAttachmentKeyIfEmpty,
   getItemById,
   hasSyncedInitialConfigurationItem,
-  removeItemById,
+  lastAvatarUploadTimestamp,
 } from '../../data/data';
 import { OnionPaths } from '../../session/onions';
 import { getMessageQueue } from '../../session/sending';
@@ -29,11 +31,18 @@ import { useInterval } from '../../hooks/useInterval';
 import { clearSearch } from '../../state/ducks/search';
 import { showLeftPaneSection } from '../../state/ducks/section';
 
-import { cleanUpOldDecryptedMedias } from '../../session/crypto/DecryptedAttachmentsManager';
+import {
+  cleanUpOldDecryptedMedias,
+  getDecryptedMediaUrl,
+} from '../../session/crypto/DecryptedAttachmentsManager';
 import { OpenGroupManagerV2 } from '../../opengroup/opengroupV2/OpenGroupManagerV2';
 import { loadDefaultRooms } from '../../opengroup/opengroupV2/ApiUtil';
 import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool';
 import { SwarmPolling } from '../../session/snode_api/swarmPolling';
+import { IMAGE_JPEG } from '../../types/MIME';
+import { FSv2 } from '../../fileserver';
+import { stringToArrayBuffer } from '../../session/utils/String';
+import { debounce } from 'underscore';
 // tslint:disable-next-line: no-import-side-effect no-submodule-imports
 
 export enum SectionType {
@@ -45,6 +54,20 @@ export enum SectionType {
   Moon,
 }
 
+const showUnstableAttachmentsDialogIfNeeded = async () => {
+  const alreadyShown = (await getItemById('showUnstableAttachmentsDialog'))?.value;
+
+  if (!alreadyShown) {
+    window.confirmationDialog({
+      title: 'File server update',
+      message:
+        "We're upgrading the way files are stored. File transfer may be unstable for the next 24-48 hours.",
+    });
+
+    await createOrUpdateItem({ id: 'showUnstableAttachmentsDialog', value: true });
+  }
+};
+
 const Section = (props: { type: SectionType; avatarPath?: string }) => {
   const ourNumber = useSelector(getOurNumber);
   const unreadMessageCount = useSelector(getUnreadMessageCount);
@@ -149,6 +172,81 @@ const triggerSyncIfNeeded = async () => {
   }
 };
 
+const triggerAvatarReUploadIfNeeded = async () => {
+  const lastTimeStampAvatarUpload = (await getItemById(lastAvatarUploadTimestamp))?.value || 0;
+
+  if (Date.now() - lastTimeStampAvatarUpload > DAYS * 14) {
+    window.log.info('Reuploading avatar...');
+    // reupload the avatar
+    const ourConvo = ConversationController.getInstance().get(UserUtils.getOurPubKeyStrFromCache());
+    if (!ourConvo) {
+      window.log.warn('ourConvo not found... This is not a valid case');
+      return;
+    }
+    const profileKey = window.textsecure.storage.get('profileKey');
+    if (!profileKey) {
+      window.log.warn('our profileKey not found... This is not a valid case');
+      return;
+    }
+
+    const currentAttachmentPath = ourConvo.getAvatarPath();
+
+    if (!currentAttachmentPath) {
+      window.log.warn('No attachment currently set for our convo.. Nothing to do.');
+      return;
+    }
+
+    const decryptedAvatarUrl = await getDecryptedMediaUrl(currentAttachmentPath, IMAGE_JPEG);
+
+    if (!decryptedAvatarUrl) {
+      window.log.warn('Could not decrypt avatar stored locally..');
+      return;
+    }
+    const response = await fetch(decryptedAvatarUrl);
+    const blob = await response.blob();
+    const decryptedAvatarData = await blob.arrayBuffer();
+
+    if (!decryptedAvatarData?.byteLength) {
+      window.log.warn('Could not read blob of avatar locally..');
+      return;
+    }
+
+    const encryptedData = await window.textsecure.crypto.encryptProfile(
+      decryptedAvatarData,
+      profileKey
+    );
+
+    const avatarPointer = await FSv2.uploadFileToFsV2(encryptedData);
+    let fileUrl;
+    if (!avatarPointer) {
+      window.log.warn('failed to reupload avatar to fsv2');
+      return;
+    }
+    ({ fileUrl } = avatarPointer);
+
+    ourConvo.set('avatarPointer', fileUrl);
+
+    // this encrypts and save the new avatar and returns a new attachment path
+    const upgraded = await window.Signal.Migrations.processNewAttachment({
+      isRaw: true,
+      data: decryptedAvatarData,
+      url: fileUrl,
+    });
+    const newAvatarPath = upgraded.path;
+    // Replace our temporary image with the attachment pointer from the server:
+    ourConvo.set('avatar', null);
+    const existingHash = ourConvo.get('avatarHash');
+    const displayName = ourConvo.get('profileName');
+    // this commits already
+    await ourConvo.setLokiProfile({ avatar: newAvatarPath, displayName, avatarHash: existingHash });
+    const newTimestampReupload = Date.now();
+    await createOrUpdateItem({ id: lastAvatarUploadTimestamp, value: newTimestampReupload });
+    window.log.info(
+      `Reuploading avatar finished at ${newTimestampReupload}, newAttachmentPointer ${fileUrl}`
+    );
+  }
+};
+
 /**
  * This function is called only once: on app startup with a logged in user
  */
@@ -158,6 +256,7 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
     void OnionPaths.buildNewOnionPathsOneAtATime();
   }
 
+  void showUnstableAttachmentsDialogIfNeeded();
   // init the messageQueue. In the constructor, we add all not send messages
   // this call does nothing except calling the constructor, which will continue sending message in the pipeline
   void getMessageQueue().processAllPending();
@@ -178,6 +277,8 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
 
   void loadDefaultRooms();
 
+  debounce(triggerAvatarReUploadIfNeeded, 200);
+
   // TODO: Investigate the case where we reconnect
   const ourKey = UserUtils.getOurPubKeyStrFromCache();
   SwarmPolling.getInstance().addPubkey(ourKey);
@@ -228,6 +329,11 @@ export const ActionsPanel = () => {
     void forceRefreshRandomSnodePool();
   }, DAYS * 1);
 
+  useInterval(() => {
+    // this won't be run every days, but if the app stays open for more than 10 days
+    void triggerAvatarReUploadIfNeeded();
+  }, DAYS * 1);
+
   return (
     <div className="module-left-pane__sections-container">
       <Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} />
diff --git a/ts/data/data.ts b/ts/data/data.ts
index d11731b36..275926369 100644
--- a/ts/data/data.ts
+++ b/ts/data/data.ts
@@ -60,6 +60,7 @@ export type ServerToken = {
 };
 
 export const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
+export const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
 
 const channelsToMake = {
   shutdown,
diff --git a/ts/fileserver/FileServerApiV2.ts b/ts/fileserver/FileServerApiV2.ts
index 642eba13a..1a161590b 100644
--- a/ts/fileserver/FileServerApiV2.ts
+++ b/ts/fileserver/FileServerApiV2.ts
@@ -4,9 +4,14 @@ import { parseStatusCodeFromOnionRequest } from '../opengroup/opengroupV2/OpenGr
 import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
 
 // tslint:disable-next-line: no-http-string
-export const fileServerV2URL = 'http://88.99.175.227';
-export const fileServerV2PubKey =
+export const oldFileServerV2URL = 'http://88.99.175.227';
+export const oldFileServerV2PubKey =
   '7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69';
+// tslint:disable-next-line: no-http-string
+export const fileServerV2URL = 'http://filev2.getsession.org';
+
+export const fileServerV2PubKey =
+  'da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59';
 
 export type FileServerV2Request = {
   method: 'GET' | 'POST' | 'DELETE' | 'PUT';
@@ -14,6 +19,7 @@ export type FileServerV2Request = {
   // queryParams are used for post or get, but not the same way
   queryParams?: Record<string, any>;
   headers?: Record<string, string>;
+  isOldV2server?: boolean; // to remove in a few days
 };
 
 const FILES_ENDPOINT = 'files';
@@ -67,7 +73,8 @@ export const uploadFileToFsV2 = async (
  * @returns the data as an Uint8Array or null
  */
 export const downloadFileFromFSv2 = async (
-  fileIdOrCompleteUrl: string
+  fileIdOrCompleteUrl: string,
+  isOldV2server: boolean
 ): Promise<ArrayBuffer | null> => {
   let fileId = fileIdOrCompleteUrl;
   if (!fileIdOrCompleteUrl) {
@@ -75,13 +82,19 @@ export const downloadFileFromFSv2 = async (
     return null;
   }
 
-  const completeUrlPrefix = `${fileServerV2URL}/${FILES_ENDPOINT}/`;
-  if (fileIdOrCompleteUrl.startsWith(completeUrlPrefix)) {
-    fileId = fileId.substr(completeUrlPrefix.length);
+  const oldCompleteUrlPrefix = `${oldFileServerV2URL}/${FILES_ENDPOINT}/`;
+  const newCompleteUrlPrefix = `${fileServerV2URL}/${FILES_ENDPOINT}/`;
+
+  if (fileIdOrCompleteUrl.startsWith(newCompleteUrlPrefix)) {
+    fileId = fileId.substr(newCompleteUrlPrefix.length);
+  } else if (fileIdOrCompleteUrl.startsWith(oldCompleteUrlPrefix)) {
+    fileId = fileId.substr(oldCompleteUrlPrefix.length);
   }
+
   const request: FileServerV2Request = {
     method: 'GET',
     endpoint: `${FILES_ENDPOINT}/${fileId}`,
+    isOldV2server,
   };
 
   const result = await sendApiV2Request(request);
@@ -119,7 +132,11 @@ export const buildUrl = (request: FileServerV2Request | OpenGroupV2Request): URL
   if (isOpenGroupV2Request(request)) {
     rawURL = `${request.server}/${request.endpoint}`;
   } else {
-    rawURL = `${fileServerV2URL}/${request.endpoint}`;
+    if (request.isOldV2server) {
+      rawURL = `${oldFileServerV2URL}/${request.endpoint}`;
+    } else {
+      rawURL = `${fileServerV2URL}/${request.endpoint}`;
+    }
   }
 
   if (request.method === 'GET') {
diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts
index c90402ca6..f98259e7d 100644
--- a/ts/models/conversation.ts
+++ b/ts/models/conversation.ts
@@ -179,7 +179,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
     //start right away the function is called, and wait 1sec before calling it again
     this.markRead = _.debounce(this.markReadBouncy, 1000, { leading: true });
     // Listening for out-of-band data updates
-    this.on('ourAvatarChanged', avatar => this.updateAvatarOnPublicChat(avatar));
 
     this.typingRefreshTimer = null;
     this.typingPauseTimer = null;
@@ -783,23 +782,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
     return null;
   }
 
-  public async updateAvatarOnPublicChat({ url, profileKey }: any) {
-    if (!this.isPublic()) {
-      return;
-    }
-    // Always share avatars on PublicChat
-
-    if (profileKey && typeof profileKey !== 'string') {
-      // eslint-disable-next-line no-param-reassign
-      // tslint:disable-next-line: no-parameter-reassignment
-      profileKey = fromArrayBufferToBase64(profileKey);
-    }
-    const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(this.get('server'));
-    if (!serverAPI) {
-      return;
-    }
-    await serverAPI.setAvatar(url, profileKey);
-  }
   public async bouncyUpdateLastMessage() {
     if (!this.id) {
       return;
@@ -1227,11 +1209,11 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
     // Not sure if we care about updating the database
   }
 
-  public async setProfileAvatar(avatar: any, avatarHash?: string) {
+  public async setProfileAvatar(avatar: null | { path: string }, avatarHash?: string) {
     const profileAvatar = this.get('avatar');
     const existingHash = this.get('avatarHash');
     let shouldCommit = false;
-    if (profileAvatar !== avatar) {
+    if (!_.isEqual(profileAvatar, avatar)) {
       this.set({ avatar });
       shouldCommit = true;
     }
diff --git a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts
index c39c40cc2..6303ae261 100644
--- a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts
+++ b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts
@@ -58,7 +58,7 @@ const getDestinationPubKey = async (
     }
   } else {
     // this is a fileServer call
-    return FSv2.fileServerV2PubKey;
+    return request.isOldV2server ? FSv2.oldFileServerV2PubKey : FSv2.fileServerV2PubKey;
   }
 };
 
diff --git a/ts/receiver/attachments.ts b/ts/receiver/attachments.ts
index 57d845009..1e690e734 100644
--- a/ts/receiver/attachments.ts
+++ b/ts/receiver/attachments.ts
@@ -23,18 +23,19 @@ export async function downloadAttachment(attachment: any) {
     serverUrl
   );
   // is it an attachment hosted on the file server v2 ?
+  const defaultFsOldV2 = _.startsWith(serverUrl, FSv2.oldFileServerV2URL);
   const defaultFsV2 = _.startsWith(serverUrl, FSv2.fileServerV2URL);
 
   let res: ArrayBuffer | null = null;
 
-  if (defaultFsV2) {
+  if (defaultFsV2 || defaultFsOldV2) {
     let attachmentId = attachment.id;
     if (!attachmentId) {
       // try to get the fileId from the end of the URL
       attachmentId = attachment.url;
     }
     window?.log?.info('Download v2 file server attachment');
-    res = await FSv2.downloadFileFromFSv2(attachmentId);
+    res = await FSv2.downloadFileFromFSv2(attachmentId, defaultFsOldV2);
   } else {
     window.log.warn(
       'downloadAttachment attachment is neither opengroup attachment nor fsv2... Dropping it'
diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts
index 55bf2dd56..a30fc03bc 100644
--- a/ts/test/types/Attachment_test.ts
+++ b/ts/test/types/Attachment_test.ts
@@ -47,7 +47,7 @@ describe('Attachment', () => {
           contentType: MIME.VIDEO_QUICKTIME,
         };
         const actual = Attachment.getSuggestedFilename({ attachment });
-        const expected = 'session-attachment.mov';
+        const expected = 'funny-cat.mov';
         assert.strictEqual(actual, expected);
       });
       it('should generate a filename without timestamp but with an index', () => {
@@ -60,7 +60,7 @@ describe('Attachment', () => {
           attachment,
           index: 3,
         });
-        const expected = 'session-attachment_003.mov';
+        const expected = 'funny-cat.mov';
         assert.strictEqual(actual, expected);
       });
       it('should generate a filename with an extension if contentType is not setup', () => {
@@ -73,7 +73,7 @@ describe('Attachment', () => {
           attachment,
           index: 3,
         });
-        const expected = 'session-attachment_003.ini';
+        const expected = 'funny-cat.ini';
         assert.strictEqual(actual, expected);
       });
 
@@ -87,7 +87,7 @@ describe('Attachment', () => {
           attachment,
           index: 3,
         });
-        const expected = 'session-attachment_003.txt';
+        const expected = 'funny-cat.txt';
         assert.strictEqual(actual, expected);
       });
       it('should generate a filename with an extension if contentType is json', () => {
@@ -100,7 +100,7 @@ describe('Attachment', () => {
           attachment,
           index: 3,
         });
-        const expected = 'session-attachment_003.json';
+        const expected = 'funny-cat.json';
         assert.strictEqual(actual, expected);
       });
     });
@@ -116,14 +116,14 @@ describe('Attachment', () => {
           attachment,
           timestamp,
         });
-        const expected = 'session-attachment-2000-01-01-000000.mov';
+        const expected = 'funny-cat.mov';
         assert.strictEqual(actual, expected);
       });
     });
     context('for attachment with index', () => {
-      it('should generate a filename based on timestamp', () => {
+      it('should generate a filename based on timestamp if filename is not set', () => {
         const attachment: Attachment.AttachmentType = {
-          fileName: 'funny-cat.mov',
+          fileName: '',
           url: 'funny-cat.mov',
           contentType: MIME.VIDEO_QUICKTIME,
         };
@@ -136,6 +136,22 @@ describe('Attachment', () => {
         const expected = 'session-attachment-1970-01-01-000000_003.mov';
         assert.strictEqual(actual, expected);
       });
+
+      it('should generate a filename based on filename if present', () => {
+        const attachment: Attachment.AttachmentType = {
+          fileName: 'funny-cat.mov',
+          url: 'funny-cat.mov',
+          contentType: MIME.VIDEO_QUICKTIME,
+        };
+        const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
+        const actual = Attachment.getSuggestedFilename({
+          attachment,
+          timestamp,
+          index: 3,
+        });
+        const expected = 'funny-cat.mov';
+        assert.strictEqual(actual, expected);
+      });
     });
   });
 
diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts
index b162c267a..cdb632a37 100644
--- a/ts/types/Attachment.ts
+++ b/ts/types/Attachment.ts
@@ -334,6 +334,9 @@ export const getSuggestedFilename = ({
   timestamp?: number | Date;
   index?: number;
 }): string => {
+  if (attachment.fileName?.length > 3) {
+    return attachment.fileName;
+  }
   const prefix = 'session-attachment';
   const suffix = timestamp ? moment(timestamp).format('-YYYY-MM-DD-HHmmss') : '';
   const fileType = getFileExtension(attachment);