Mustafa Alawami
Mustafa Alawami

Reputation: 57

how to send attach a file from firebase storage and send it to an email using mailgun services in node.js

Is it possible to attach a file from the firebase storage?? I tried the following code but it doesn't work

var mailgun = require("mailgun-js");
var api_key = 'key-acf9f881e32c85b3c0dad34358507a95';
var DOMAIN = 'sandbox76c6f74ddab14862816390c16f37a272.mailgun.org';
var mailgun = require('mailgun-js')({apiKey: api_key, domain: DOMAIN});
var path = require("path");

var filepath = path.join(`gs://i-m-here-c01f6.appspot.com/Groups/${leaderId}`, 'group_image.jpg');


var data = {
    from: 'Excited User <[email protected]>',
    to: '[email protected]',
    subject: 'Complex',
    text: 'Group Creation Request',
    html: `<p>A user named: ${fromName} wants to create a group.<br />
            User ID: ${leaderId}<br />
            Group Name: ${groupName}<br />
            Group Description: ${groupDescription}<br /><br />
            To Accept the request click here:<br />
            https://us-central1-i-m-here-c01f6.cloudfunctions.net/acceptOrDenyGroupCreation?leaderID=${leaderId}&requestStatus=approved <br /><br />
            To Deny the request click here:<br />
            https://us-central1-i-m-here-c01f6.cloudfunctions.net/acceptOrDenyGroupCreation?leaderID=${leaderId}&requestStatus=denied /></p>`,
    attachment: filepath
};

mailgun.messages().send(data, function (error, body) {
    if(error)
        console.log('email err: ',error);
});

please help

Upvotes: 2

Views: 5197

Answers (4)

Daniel Danielecki
Daniel Danielecki

Reputation: 10522

[That's an example for Nodemailer, it took me so much time to attach dynamically an attachment from a Cloud Storage for Firebase. Therefore, hopefully it'll help out someone. You can find so many similarities.

All the examples are in Angular / TypeScript.

It starts somewhere in the component.html file

<form
  #formDirective="ngForm"
  [formGroup]="contactForm"
  (ngSubmit)="onSubmit(contactForm.value, formDirective)"
>
  <mat-form-field>
    <ngx-mat-file-input
      (change)="uploadFile($event)"
      formControlName="fileUploader"
      multiple
      type="file"
    >
    </ngx-mat-file-input>
  </mat-form-field>
</form>

In component.ts

import { AngularFirestore } from '@angular/fire/firestore';
import {
  AngularFireStorage,
  AngularFireStorageReference,
  AngularFireUploadTask,
} from '@angular/fire/storage';
import {
  FormBuilder,
  FormGroup,
  FormGroupDirective,
  Validators,
} from '@angular/forms';
import { throwError } from 'rxjs';
...

constructor(
  private angularFirestore: AngularFirestore,
  private angularFireStorage: AngularFireStorage,
  private formBuilder: FormBuilder
) {}

public contentType: string[] = [];
public downloadURL: string[] = [];
public fileName: string = '';
public maxFileSize = 20971520;

public contactForm: FormGroup = this.formBuilder.group({
  fileUploader: [
    '',
    Validators.compose([
      // Your validators rules...
    ]),
  ],
  ...
});
...

public onSubmit(form: any, formDirective: FormGroupDirective): void {
  form.contentType = this.contentType;
  form.fileUploader = this.downloadURL;
  form.fileName = this.fileName;

  this.angularFirestore
    .collection(String(process.env.FIRESTORE_COLLECTION_MESSAGES)) // Make sure the environmental variable is a string.
    .add(form)
    .then(() => {
      // Your logic, such as alert...
    .catch(() => {
      // Your error handling logic...  
    });
}

public uploadFile(event: any): void {
  // Iterate through all uploaded files.
  for (let i = 0; i < event.target.files.length; i++) {
    const file = event.target.files[i]; // Get each uploaded file.
    const fileName = file.name + '_' + Date.now(); // It makes sure files with the same name will be uploaded more than once and each of them will have unique ID, showing date (in milliseconds) of the upload.

    this.contentType = file.type;
    this.fileName = fileName;

    // Get file reference.
    const fileRef: AngularFireStorageReference = this.angularFireStorage.ref(
      fileName
    );

    // Create upload task.
    const task: AngularFireUploadTask = this.angularFireStorage.upload(
      fileName,
      file,
      file.type
    );

    // Upload file to Cloud Firestore.
    task
      .snapshotChanges()
      .pipe(
        finalize(() => {
          fileRef.getDownloadURL().subscribe((downloadURL: string) => {
            this.angularFirestore
              .collection(String(process.env.FIRESTORE_COLLECTION_FILES)) // Make sure the environmental variable is a string.
              .add({ downloadURL: downloadURL });
            this.downloadURL.push(downloadURL);
          });
        }),
        catchError((error: any) => {
          return throwError(error);
        })
      )
      .subscribe();
  }
}

That's all frontend, now it's finally time for our backend.

import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore';
import { EventContext } from 'firebase-functions';

async function onCreateSendEmail(
  snap: DocumentSnapshot,
  _context: EventContext
) {
  try {
    const contactFormData = snap.data();

    // You can use those to debug your code...
    console.log('Submitted contact form: ', contactFormData);
    console.log('context: ', _context); // This log will be shown in Firebase Functions logs.

    const mailTransport: Mail = nodemailer.createTransport({
      // Make sure the environmental variables have proper typings.
      host: String(process.env.MAIL_HOST),
      port: Number(process.env.MAIL_PORT),
      auth: {
        user: String(process.env.MAIL_ACCOUNT),
        pass: String(process.env.MAIL_PASSWORD),
      },
      tls: {
        rejectUnauthorized: false, //! Fix ERROR "Hostname/IP doesn't match certificate's altnames".
      },
    });

    const mailOptions = {
      attachments: [
        {
          contentType: `${contactFormData!.contentType}`,
          filename: `${contactFormData!.fileName}`,
          path: `${contactFormData!.fileUploader}`,
        },
      ],
      ... // Your other mails options such as bcc, from, to, subject, html...
    };

    await mailTransport.sendMail(mailOptions);
  } catch (err) {
    console.error(err);
  }
}

That's more or less all the headache from frontend to backend a developer has to go through to dynamically attach file to an email with correct contentType, downloadURL, and fileName. I was missing a complete solution for the case across WWW, that's why the front-to-back answer.

Note: The frontend is handling multiple file uploads on the UI/Cloud Storage for Firebase side and all the files are uploaded to Firebase. However, only one dynamically added attachment is working fine. I still can't figure out how to handle multiple dynamic files uploads.

Upvotes: 0

Ariel Salem
Ariel Salem

Reputation: 357

In the example you've given you are using the gs:://bucket-name/path to download the file from Cloud Storage and send it as an attachment. In order to do so, you would need to use the request dependency as per the docs:

https://github.com/bojand/mailgun-js#attachments

In this case, all you would have to do is:

// var path = require("path");
const request = require('request');

var filepath = request(`gs://i-m-here-c01f6.appspot.com/Groups/${leaderId}`);

If you wanted to get more specific about assigning properties to the file, you can use the new mailgun.Attachments(options) to pass in an options parameter that looks something like this:

options: {
  data: filepath,
  filename: 'name,jpg',
  contentType: 'image/jpeg',
  knownLength: 2019121,
};

Where

  • data - can be one of:
    • a string representing file path to the attachment
    • a buffer of file data
    • an instance of Stream which means it is a readable stream.
  • filename - the file name to be used for the attachment. Default is 'file'
  • contentType - the content type. Required for case of Stream data. Ex. image/jpeg.
  • knownLength - the content length in bytes. Required for case of Stream data.

Upvotes: 0

Netanel R
Netanel R

Reputation: 136

You can get it as a buffer and send it like this :

var request = require('request');
var file = request("https://www.google.ca/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");

var data = {
  from: 'Excited User <[email protected]>',
  to: '[email protected]',
  subject: 'Hello',
  text: 'Testing some Mailgun awesomeness!',
  attachment: file
};

mailgun.messages().send(data, function (error, body) {
  console.log(body);
});

from here

Upvotes: 2

Doug Stevenson
Doug Stevenson

Reputation: 317477

You can't use a gs://bucket-name/path-to-file URL to download a file from Cloud Storage just like it was an HTTP URL. Instead, you'll have to do one of these:

  1. Use the Cloud Storage SDK to download the file locally, then attach it to your email
  2. Or, use the Cloud Storage SDK to generate a "Signed URL", which will give you an HTTPS URL to the file, which can be used to download it.

Upvotes: 4

Related Questions