Skip to content

Instantly share code, notes, and snippets.

@hdornier
Last active January 29, 2024 20:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hdornier/e04d04921032e98271f46ff8a539a4cb to your computer and use it in GitHub Desktop.
Save hdornier/e04d04921032e98271f46ff8a539a4cb to your computer and use it in GitHub Desktop.
Front API Attachment Example

When you want to send files through the API, your request needs to be sent using the Content-Type HTTP header set to multipart/form-data. The JSON data you would normally send in the request body needs to be split in multiple form fields (one for each property) named after the property key. If the property is an array, the name should include the index of the data (ie: {"to": [email1@example.com, email1@example.com]} would be split in two fields named to[0], to[1]).

Below is an example template demonstrating how to send a message via Front's Create conversation API endpoint:

curl -X POST 'https://api2.frontapp.com/channels/FRONT_CHANNEL_ID/messages' \
-H 'Authorization: Bearer YOUR_FRONT_API_TOKEN' \
-H 'Content-Type: multipart/form-data' \
--form 'author_id=AUTHOR_ID' \
--form 'to[0]=RECIPIENT_HANDLE' \
--form 'attachments=@PATH_TO_FILE' \
--form 'body=MESSAGE_BODY'

For a practical example, below you can see an example where the VARIABLES have been populated with example values:

curl -X POST 'https://api2.frontapp.com/channels/cha_123/messages' \
-H 'Authorization: Bearer 7ihw3d73wi7ehfw84...' \
-H 'Content-Type: multipart/form-data' \
--form 'author_id=tea_123' \
--form 'to[0]=+14155550000' \
--form 'attachments=@/Users/me/Photos/cat.jpg' \
--form 'body=This is an SMS message with an attached photo of a cat'

The following provides an example for Node:

const FRONT_API_TOKEN = 'YOUR FRONT API TOKEN GOES HERE';
const CHANNEL_ID = 'YOUR FRONT CHANNEL ID GOES HERE';

const FormData = require('form-data');
const fs = require('fs');

// abstract and promisify actual network request
async function makeRequest(formData, options) {
  return new Promise((resolve, reject) => {
    const req = formData.submit(options, (err, res) => {
      if (err)
        return reject(new Error(err.message))

      if (res.statusCode < 200 || res.statusCode > 299)
        return reject(new Error(`HTTP status code ${res.statusCode}`))

      const body = [];
      res.on('data', (chunk) => body.push(chunk));
      res.on('end', () => {
        const resString = Buffer.concat(body).toString();
        resolve(resString);
      })
    })
  })
}

const formData = new FormData()

// Set your data here: (See full options at https://dev.frontapp.com/reference/messages-1#post_channels-channel-id-messages)
formData.append('subject', 'Message subject');
formData.append('to[0]', 'recipient@example.com');
formData.append('to[1]', 'recipient2@example.com');
formData.append('sender_name', 'Support');
formData.append('body', '<p>Message body</p>');
formData.append('attachments[0]', fs.createReadStream('./photo.jpg'));

const options = {
  host: 'api2.frontapp.com',
  path: `/channels/${CHANNEL_ID}/messages`,
  method: 'POST',
  protocol: 'https:', // note : in the end
  headers: {
    Authorization: `Bearer ${FRONT_API_TOKEN}`
  },
}

async function run() {
  const res = await makeRequest(formData, options);
  console.log(res);
}

run()
@ZoranRavic
Copy link

URL for docs of Front's Create conversation API endpoint was changed to https://dev.frontapp.com/reference/post_channels-channel-id-messages

Docs for Receive custom messages also lead here, but there is a gist specifically for that API:
https://gist.github.com/dugjason/a81466eb617879676bc69c76a6ba1bd6

@hdornier
Copy link
Author

@ZoranRavic Thanks for the note, updated!

@Louis95
Copy link

Louis95 commented May 17, 2022

@ZoranRavic @hdornier

is there a way to make the attachment link available to unauthenticated users?

I tried using the download link but it didn't work. I'll be happy if you could share any resources that can be helpful.

Thank you.

@hdornier
Copy link
Author

@Louis95 there's no way to make attachments available in an unauthenticated fashion through the Front API today, but feel free to reach out to us at api@frontapp.com if you have more details about your use case and we can create a feature request.

@evepoe
Copy link

evepoe commented Jan 3, 2023

How do I attach the file content instead of passing the reader?
let buffer = readBufferSomewhere()
Can I just put the buffer.toString() into the attachments?

@ZoranRavic
Copy link

@evepoe You can pass the buffer without the .toString().

FormData supports both ReadableStream and Buffer as a value.

formData.append(
    `attachments[0]`,
    bufferOrReadableStream,
    {
        filename: attachmentFilename,
        contentType: attachmentContentType,
    }
);

Something that is not included in the example above is that you can (optionally) specify the file name and content type for the attachment.

@Floriferous
Copy link

Floriferous commented Jan 11, 2023

Do you have to change options: { archive: true } into "options.archive=true" ? Or "options[archive]=true"?

And what about options: { tag_ids: ["some-tag-id"] }, not sure about what the key of that tag ID would be like?

@dugjason
Copy link

Hi @Floriferous , great question.

Do you have to change options: { archive: true } into "options.archive=true" ? Or "options[archive]=true"?

When submitting via curl, you would submit:

--form "options[archive]=true"

And what about options: { tag_ids: ["some-tag-id"] }

We're submitting an array of tags here, so the following solutions works to add multiple elements to an array:

--form "options[tag_ids][]=tag_123"
--form "options[tag_ids][]=tag_456"

@Eggwise
Copy link

Eggwise commented Feb 7, 2023

Python peeps!! 🐍

Took me some time to figure out how to make it work with python, but for other people who want to make requests to front api with files see my example. Its not mega complete (i ripped it out of my client class and put it in a script) but it works and gives you an idea how to implement it in your python front api client.

Cheers

import requests
from requests_toolbelt import MultipartEncoder


def __read_files(files: list[dict[str, str]]):
    """Reads the files based on the path provided in the dict[path] and adds it as the data prop to the dict"""
    read_files = []
    for f in files:
        d = open(f["path"], "rb")
        read_files.append({**f, "data": d})

    return read_files


def __build_multipart_form(data, files) -> MultipartEncoder:
    fields = {}

    data["attachments"] = [(f["name"], f["data"]) for f in files]  # list of tuples

    for k, v in data.items():
        if isinstance(v, list):
            for index, i in enumerate(v):
                fields[f"{k}[{index}]"] = i
            continue

        if isinstance(v, dict):
            for dk, dv in v.items():
                if isinstance(dv, dict):
                    # todo support deep dicts via recursion
                    raise AttributeError(
                        f"Deep dicts not yet supported when sending requests with files"
                    )
                fields[f"{k}[{dk}]"] = dv
            continue

        if isinstance(v, bool):
            fields[k] = "false" if v is False else "true"
            continue

        # default string
        fields[k] = v

    m = MultipartEncoder(fields=fields)
    return m


def _request_with_files(
        method: str,
        url: str,
        data: dict,
        token: str,
        files: list[dict[str, str]],
        headers: dict = {},
        **kwargs,
):
    """
    Calls an url with multipart form data prepared for front api
    @param method: get, post, patch, put
    @param url:
    @param data:
    @param files: list of dicts with name and path keys
    @return:
    """
    # https://gist.github.com/hdornier/e04d04921032e98271f46ff8a539a4cb

    files = __read_files(files)
    m = __build_multipart_form(data, files)
    headers = {
        **headers,
        "Content-Type": m.content_type,
        "Authorization": f"Bearer {token}",
    }

    request_func = getattr(requests, method.lower())  # get, post, put etc
    response = request_func(url=url, data=m, headers=headers)
    return response.json()


def test_send_message_with_files():
    channel_id = "cha_xxx"

    message = {
        "to": ["mail@test.com"],
        "subject": "Subject",
        "body": "Some text",
        "channel_id": channel_id,
        "signature_id": "sig_xxx",
        "archive": False,
    }

    token = "token here"
    method = "post"
    files = [{"name": "such_file.txt", "path": "test.txt"}]

    url = f"https://api2.frontapp.com/channels/{channel_id}/messages"

    result = _request_with_files(
        url=url, method=method, data=message, files=files, token=token
    )

    print(result)

shoutout to softwarevrienden.nl

@Passer-Keenan
Copy link

Passer-Keenan commented Aug 16, 2023

Was attempting to do this in Python and was running into a couple issues with the main points being:

  • Do not manually set the Content-Type header due to issues
  • Move the list of files you are including into the files parameter in your request
  • Ensure you are passing the parameter data and not json

Here is an example:

def draft_with_attachments(data: dict, files: dict):
    resp = requests.post(
        f"{FRON_URL}/channels/{channel_id}/drafts",
        headers={
            "Accept": "application/json",
            "Authorization": f"Bearer {API_TOKEN}",
        },
        data=data, # Should NOT be json=...
        files=files,
)


# attachments is a list of decoded files
def draft_a_new_email(subject: str, message_body: str, author: str, attachments: list, sending_to: list):
    files = {}
    to_emails = {}
    for ind, attachment in enumerate(attachments):
        files[f"attachments[{ind}]"] = attachment["contents"]
    for ind, email in enumerate(sending_to):
        to_emails[f"to[{ind}]"] = email
    
    data={
        "body": message_body,
        "author_id": author,
        "subject": subject,
    }
    data.update(to_emails)
    draft_with_attachments(data, files)
    

@klapperkopp
Copy link

What's the limitation on file types and file sizes for attachments? I cannot find it anywhere.

@dugjason
Copy link

What's the limitation on file types and file sizes for attachments?

Max attachment size varies on a per-channel basis. Documented here: https://help.front.com/en/articles/2158
Most image / PDF / document file types are accepted (Word / Excel / MP4 etc).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment