Skip to content

Instantly share code, notes, and snippets.

@astoiccoder
Last active March 27, 2024 22:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save astoiccoder/77577ea58eaf2a9b91cee8ca78c0a6f0 to your computer and use it in GitHub Desktop.
Save astoiccoder/77577ea58eaf2a9b91cee8ca78c0a6f0 to your computer and use it in GitHub Desktop.
reMarkable to Notion Sync
const GMAIL_LABEL_NAME = 'NotionToSync';
const SYNCED_LABEL = 'SyncedToNotion';
// if you set this, the attachments will be copied over to google drive and the links added to the notion page
const GDRIVE_FOLDER_ID = '';
const gmailToNotion = () => {
const label = GmailApp.getUserLabelByName(GMAIL_LABEL_NAME);
const successLabel = GmailApp.getUserLabelByName(SYNCED_LABEL);
label.getThreads(0, 20).forEach((thread) => {
const [message] = thread.getMessages().reverse();
postToNotion(message);
thread.removeLabel(label);
thread.addLabel(successLabel)
});
};
function getRichTextChunks(messageBody) {
let remainingString = messageBody;
const content = [];
while (remainingString.length > 0) {
// https://developers.notion.com/reference/request-limits#limits-for-property-values
if (remainingString.length <= 2000) {
content.push(getRichTextObjectForChunk(remainingString));
remainingString = '';
} else {
const maximalChunk = remainingString.substring(0, 2000);
const lastLineBreakInChunk = maximalChunk.lastIndexOf('\n');
const actualChunk = remainingString.substring(0, lastLineBreakInChunk);
content.push(getRichTextObjectForChunk(actualChunk));
remainingString = remainingString.substring(lastLineBreakInChunk + 1);
}
}
return content;
}
function getRichTextObjectForChunk(messageChunk) {
return {
type: 'text',
text: {
content: messageChunk
},
}
}
function getPdfBlocksForAttachments(message) {
var attachments = message.getAttachments();
var pdfBlocks = [];
for (var k = 0; k < attachments.length; k++) {
Logger.log('Message contains the attachment "%s" (%s bytes)', attachments[k].getName(), attachments[k].getSize());
var folder = DriveApp.getFolderById(GDRIVE_FOLDER_ID);
var ts = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyMMddHHmmss");
var createdFile = folder.createFile(attachments[k].copyBlob()).setName(attachments[k].getName() + ts);
pdfBlocks.push({
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [
{
"type": "text",
"text": {
"content": attachments[k].getName(),
"link": {
"url": createdFile.getUrl()
}
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": attachments[k].getName(),
"href": createdFile.getUrl()
}
],
"color": "default"
}
})
}
}
function postToNotion(message) {
const richTextChunks = getRichTextChunks(message.getPlainBody());
const pdfBlocks = GDRIVE_FOLDER_ID ? getPdfBlocksForAttachments(message) : [];
const url = 'https://api.notion.com/v1/pages';
const body = {
parent: {
type: "database_id",
database_id: "<Your Target Notion Database Id>",
},
icon: {
type: "emoji",
emoji: "📝"
},
children: [
...pdfBlocks,
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: richTextChunks,
},
}
],
properties: {
Name: {
title: [
{
text: {
content: message.getSubject(),
},
},
],
}
}
}
UrlFetchApp.fetch(url, {
method: 'post',
contentType: "application/json",
muteHttpExceptions: false,
headers: {
Authorization: `Bearer <Your Notion Secret>`,
'Notion-Version': '2022-02-22'
},
payload: JSON.stringify(body)
});
}
@doggiecuddles
Copy link

Hey @astoiccoder thank you so much for building this!

Everything looks good on my end but it is not showing up in notion

@astoiccoder
Copy link
Author

Hey @astoiccoder thank you so much for building this!

Everything looks good on my end but it is not showing up in notion

Hi @doggiecuddles,
A few things important to check could be:

  1. Did you use the "Text in email" option when sending the note from the remarkable?
  2. Are both labels ('NotionToSync' and 'SyncedToNotion') applied correctly on the emails that are being sent? That's a first indicator if the script is actually running through.
  3. Do you see any errors in the google apps script? (If the request to create the page in notion fails, it should show an error in the script - in order to check that you can also manually run the script after sending a fresh email or removing the 'SyncedToNotion' label on any prior email and run the script again)

@anthony88088
Copy link

hello! i'm struggling with this right now,
Exception: Request failed for https://api.notion.com returned code 401. Truncated server response: {"object":"error","status":401,"code":"unauthorized","message":"API token is invalid.","request_id":

getting the above response. nothing shows up in the database. i use convert to text and when emailing "text to email"

@samlee888
Copy link

Thanks for doing this @astoiccoder! I love notion and recently gotten my hands on Remarkable. Two truly amazing tools which will be even better if integrated OOTB.
Thanks for building this integration!

Using the latest code, I face a slightly different error.

"Exception: Request failed for https://api.notion.com returned code 400. Truncated server response: {"object":"error","status":400,"code":"validation_error","message":"body failed validation. Fix one:\nbody.parent.page_id should be defined, instea... (use muteHttpExceptions option to examine full response)"

Upon looking into this link "ramnes/notion-sdk-py#72", it seems like property values of the new page in the properties parameter must conform to the parent database's property schema. Any suggestion how to do this?

Thanks in advanced!


Exception: Request failed for https://api.notion.com/ returned code 400. Truncated server response: {"object":"error","status":400,"code":"validation_error","message":"body failed validation. Fix one:\nbody.parent.page_id should be defined, instea... (use muteHttpExceptions option to examine full response)

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