When utilizing gapi (typescript) to send emails, the Sent box may contain incorrectly encoded email messages

When I send emails using gapi, the receiver receives them correctly. However, when I check my "Sent" box, the emails appear as Chinese gibberish characters like in this image.

This issue only occurs when using non-gmail applications. If I view the Sent box in gmail, everything looks fine. But when I use Outlook or my Android's default Mail app connected to my gmail account, the content is completely messed up. It seems to be an encoding problem that Gmail can handle but other apps cannot.

Here's the code snippet I'm using to send emails:

async sendMail(email, files) {
    //create cc and bcc string if needed
    let ccStr = `Cc: ${email.cc}\r\n`
    let bccStr = `Bcc: ${email.bcc}\r\n`

    //create attachment if needed
    let attachStr = await this.createAttachmentStr(files)
    attachStr = `${attachStr}\r\n\r\n`

    //create the message
    let message = 'Content-Type: multipart/mixed; boundary="#split-part#"\r\n' +
      'MIME-Version: 1.0\r\n' +
      `To: ${email.to}\r\n` +
      `From: ${email.from}\r\n` +
      ccStr +
      bccStr +
      `Subject: ${email.subject}\r\n\r\n` +
      '--#split-part#\r\n' +
      'Content-Type: text/html; charset="UTF-8"\r\n' +
      'MIME-Version: 1.0\r\n' +
      'Content-Transfer-Encoding: base64\r\n\r\n' +
      `${email.message}\r\n\r\n` +
      `${attachStr}` +
      '--#split-part#--'

    //base64url encode
    const encodedMessage = btoa(unescape(encodeURIComponent(message))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')

    //send to api
    gapi.client["gmail"].users.messages.send({
      'userId': 'me',
      'resource': {
        'raw': encodedMessage,
        "payload": {
          "mimeType": 'multipart/mixed'
        },
        'threadId': email.id
      }
    }).then(async response => {
      //do some stuff after
    })
  }

The method used in there called createAttachmentStr based on a FileList:

createAttachmentStr(files: FileList): Promise<string> {
    return new Promise<string>((resolve) => {
      let attachStr = ""

      //initialize counter
      let count = 0

      //loop through each file
      if (files && files.length > 0) {
        Array.from(files).forEach(async file => {
          //convert file to datastrings
          let dataStr = await this.readFileContent(file)

          //get file info
          let fileName = file.name
          let contentType = file.type

          //create message str
          attachStr += '--#split-part#\r\n' +
            `Content-Type: ${contentType}\r\n` +
            'MIME-Version: 1.0\r\n' +
            'Content-Transfer-Encoding: base64\r\n' +
            `Content-Disposition: attachment; filename="${fileName}"\r\n\r\n` +
            `${dataStr}\r\n\r\n`

          //increase the counter
          count++

          if (count === files.length) {
            resolve(attachStr);
          }
        });
      }
      else resolve("")
    });
  }

This issue occurs rarely and seems to be related to specific emails with certain attachments. I'm struggling to figure out what triggers it exactly. Any help would be greatly appreciated.

Answer №1

It appears that the issue you're experiencing is possibly linked to specific attachments, indicating a problem with the email's content type header:

Content-Type: text/html; charset="UTF-8"

This suggests that one or both of the following scenarios are occurring:

  1. Some attachments (including possibly email.message) are not HTML files, which means they should not be passed to encodeURIComponent.

  2. The dataStr might not be utf-8 encoded, causing this.readFileContent(file) to interpret it incorrectly as a utf-8 encoded file when it has a different encoding.

Another possibility is that the contentType for certain attachments is creating an incorrect attachment header, either by being inaccurate, incomplete (missing the charset="UTF-8" part), or invalid in this line:

`Content-Type: ${contentType}\r\n`

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing the array.map method to access the key of an object within an array of arrays in TypeScript

Can I utilize array.map in TypeScript to apply a function with the parameter being the key of an object within an array? I have an array containing objects which have keys 'min' and 'max'. I am looking to use something like someArrayFun ...

Leveraging NestJs Libraries within Your Nx Monorepo Main Application

I am currently part of a collaborative Nx monorepo workspace. The setup of the workspace looks something like this: https://i.stack.imgur.com/zenPw.png Within the structure, the api functions as a NestJS application while the data-access-scripts-execute ...

VS Code fails to identify Typescript internal modules

I am currently facing issues with separating my TypeScript classes into distinct files using internal modules. Unfortunately, the main.ts file is not loading or recognizing the sub-modules. main.ts /// <reference path="Car.ts" /> module Vehicles { ...

Display the length of the product array when I have multiple JSON objects

I am working with the following JSON data: { "StatusCode": 0, "StatusMessage": "OK", "StatusDescription": [ { "_id": "12123", "dateCreated": "2019-12-03T13:45:30.418Z", "pharmacy_id": "011E7523455533 ...

Using Datatable.net with Angular 6 for Change Monitoring

I've been encountering several issues with the custom datatable component that I created. One specific problem is that when I delete a row from my datatable, not only does the row disappear, but also the pagination functionality and other features st ...

Using Angular: Embed environment.ts into index.html as a standalone JavaScript file

Angular is typically set up to include the environment.ts file into main.js during the build process. This means that when dealing with multiple environments and corresponding environment.ts files, we must choose the environment at build time. My goal is ...

A guide on harnessing the power of forEach in Ionic 4

I'm currently in the process of upgrading my Ionic v1 app to Ionic 4. Within my app, there is a forEach loop that needs to be adjusted for Ionic 4 compatibility. Here is the code snippet from Ionic v1: angular.forEach(this.selectedQuestion.answers, ...

In Angular, make a call to a second API if the first API call times out after a specified period

In the event that my API does not return data within 5 seconds, I need to call a different one. Attempted implementation: this.service.returnData1(param1, param2) .pipe(timeout(5000), finalize(() => this.callSecondApi())) .subscribe( data => { ...

Accessing the element reference within a custom attribute directive in Angular through a projected template using ngTemplateOutlet

I'm encountering an issue when trying to adjust the css and click event on a custom attribute directive The DIV causing the problem is nested within a reference that is projected and passed into a child component. Here is the process: Add the ng-te ...

Creating a JSON structure using an array in Typescript

Here is an example of my array structure: [ { "detail": "item1", "status": "active", "data": "item1_data" }, { "detail": "item2", "status": ...

What is the best way to retrieve the attribute value of an element using Angular 2?

I am working with an HTML span that contains two hyperlinks. <span><a href="http://appcarvers.cloudaccess.host/index.php?Itemid=207" alt="Shirley Setia">Shirley Setia</a><i class="fa fa-caret-right"></i> <a href="http:// ...

Removing a selected row from a data table using an HTTP request in Angular 2

I am working with a table that has data retrieved from the server. I need to delete a row by selecting the checkboxes and then clicking the delete button to remove it. Here is the code snippet: ...

Create a CRUD interface in Angular 5 using automated generation techniques

Is there a framework available that can assist in creating CRUD view files from a database table within an MVC project, with Angular as the front-end? I have spent hours searching on Google but still haven't found a solution. All I came across were i ...

Conceal the visibility of the eye icon within the password field in Angular 6

The current implementation of the function is operational, although it involves using a checkbox. I am seeking assistance regarding incorporating an eye icon within the password input field, preferably without relying on bootstrap or font-awesome. Any gu ...

A recursive approach for constructing a tree structure in Angular

Currently, I am working on a project involving the implementation of crud functions. To display the data in a tree-like structure, I am utilizing the org chart component from the PrimeNg library. The data obtained from the backend is in the form of an arra ...

Creating custom designs for Material UI components

Although not a major issue, there is something that bothers me. I am currently using react, typescript, and css modules along with . The problem arises when styling material ui components as I find myself needing to use !important quite frequently. Is th ...

Angular OAuth2 OIDC password reset process

Currently, I am integrating B2C into my Angular (8) application using angular-oauth2-oidc. I have successfully implemented sign-in and sign-out policies, as well as configuring the angular-oauth2-oidc service. However, when utilizing the standard Microsoft ...

Angular: how to manually trigger change detection without using Angular's dependency injection system

Is there a way to globally initiate angular change detection without having to inject something like ApplicationRef? I am looking to utilize the functionality as a standard JavaScript function rather than a service method, in order to avoid the need for ...

Encountering issues in Angular 2 when attempting to pass data into root component through ng-content and binding. Objective: Creating a reusable form component

I currently have a .NET MVC application and I'm looking to integrate Angular 2 into it. The structure of my page is as follows: <html> <head>css imports and jquery imports</head> <body> <div> a bunch of table ...

Combining existing CSS classes with node labels in Cytoscape JS for Angular: A Guide

My project follows a consistent CSS theme, but the node's CSS style doesn't match. I'm looking to adjust the label colors in the CSS based on whether it's day mode or night mode. How can I accomplish this? this.cy = cytoscape({ con ...