mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
feat(tools): created outlook tools/block (#409)
* feat: first round of tools for outlook * added more * outlook finished * added bun and docs * fix: added greptile comments * added greptile and bun lint * got rid of HTML --------- Co-authored-by: Adam Gough <adamgough@Adams-MacBook-Pro.local>
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
"microsoft_teams",
|
||||
"notion",
|
||||
"openai",
|
||||
"outlook",
|
||||
"perplexity",
|
||||
"pinecone",
|
||||
"reddit",
|
||||
|
||||
@@ -9,30 +9,70 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
type="microsoft_teams"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 2228.833 2073.333"
|
||||
>
|
||||
<path fill="#5059C9" d="M1554.637,777.5h575.713c54.391,0,98.483,44.092,98.483,98.483c0,0,0,0,0,0v524.398 c0,199.901-162.051,361.952-361.952,361.952h0h-1.711c-199.901,0.028-361.975-162-362.004-361.901c0-0.017,0-0.034,0-0.052V828.971 C1503.167,800.544,1526.211,777.5,1554.637,777.5L1554.637,777.5z"/>
|
||||
<circle fill="#5059C9" cx="1943.75" cy="440.583" r="233.25"/>
|
||||
<circle fill="#7B83EB" cx="1218.083" cy="336.917" r="336.917"/>
|
||||
<path fill="#7B83EB" d="M1667.323,777.5H717.01c-53.743,1.33-96.257,45.931-95.01,99.676v598.105 c-7.505,322.519,247.657,590.16,570.167,598.053c322.51-7.893,577.671-275.534,570.167-598.053V877.176 C1763.579,823.431,1721.066,778.83,1667.323,777.5z"/>
|
||||
<path opacity=".1" d="M1244,777.5v838.145c-0.258,38.435-23.549,72.964-59.09,87.598 c-11.316,4.787-23.478,7.254-35.765,7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833 c-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1244z"/>
|
||||
<path opacity=".2" d="M1192.167,777.5v889.978c-0.002,12.287-2.47,24.449-7.257,35.765 c-14.634,35.541-49.163,58.833-87.598,59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833 c-7.257-17.623-12.958-34.21-18.142-51.833c-18.144-59.476-27.402-121.307-27.472-183.49V877.02 c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
|
||||
<path opacity=".2" d="M1192.167,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855h-447.84 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z"/>
|
||||
<path opacity=".2" d="M1140.333,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855H649.472 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1140.333z"/>
|
||||
<path opacity=".1" d="M1244,509.522v163.275c-8.812,0.518-17.105,1.037-25.917,1.037 c-8.812,0-17.105-0.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003 c-7.153-16.715-12.706-34.071-16.587-51.833h258.648C1201.449,414.866,1243.801,457.217,1244,509.522z"/>
|
||||
<path opacity=".2" d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
|
||||
<path opacity=".2" d="M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z"/>
|
||||
<path opacity=".2" d="M1140.333,561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003 h138.395C1097.783,466.699,1140.134,509.051,1140.333,561.355z"/>
|
||||
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="198.099" y1="1683.0726" x2="942.2344" y2="394.2607" gradientTransform="matrix(1 0 0 -1 0 2075.3333)">
|
||||
<stop offset="0" stop-color="#5a62c3"/>
|
||||
<stop offset=".5" stop-color="#4d55bd"/>
|
||||
<stop offset="1" stop-color="#3940ab"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#a)" d="M95.01,466.5h950.312c52.473,0,95.01,42.538,95.01,95.01v950.312c0,52.473-42.538,95.01-95.01,95.01 H95.01c-52.473,0-95.01-42.538-95.01-95.01V561.51C0,509.038,42.538,466.5,95.01,466.5z"/>
|
||||
<path fill="#FFF" d="M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z"/>
|
||||
iconSvg={`<svg className="block-icon" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2228.833 2073.333'>
|
||||
<path
|
||||
fill='#5059C9'
|
||||
d='M1554.637,777.5h575.713c54.391,0,98.483,44.092,98.483,98.483c0,0,0,0,0,0v524.398 c0,199.901-162.051,361.952-361.952,361.952h0h-1.711c-199.901,0.028-361.975-162-362.004-361.901c0-0.017,0-0.034,0-0.052V828.971 C1503.167,800.544,1526.211,777.5,1554.637,777.5L1554.637,777.5z'
|
||||
/>
|
||||
<circle fill='#5059C9' cx='1943.75' cy='440.583' r='233.25' />
|
||||
<circle fill='#7B83EB' cx='1218.083' cy='336.917' r='336.917' />
|
||||
<path
|
||||
fill='#7B83EB'
|
||||
d='M1667.323,777.5H717.01c-53.743,1.33-96.257,45.931-95.01,99.676v598.105 c-7.505,322.519,247.657,590.16,570.167,598.053c322.51-7.893,577.671-275.534,570.167-598.053V877.176 C1763.579,823.431,1721.066,778.83,1667.323,777.5z'
|
||||
/>
|
||||
<path
|
||||
opacity='.1'
|
||||
d='M1244,777.5v838.145c-0.258,38.435-23.549,72.964-59.09,87.598 c-11.316,4.787-23.478,7.254-35.765,7.257H667.613c-6.738-17.105-12.958-34.21-18.142-51.833 c-18.144-59.477-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1244z'
|
||||
/>
|
||||
<path
|
||||
opacity='.2'
|
||||
d='M1192.167,777.5v889.978c-0.002,12.287-2.47,24.449-7.257,35.765 c-14.634,35.541-49.163,58.833-87.598,59.09H691.975c-8.812-17.105-17.105-34.21-24.362-51.833 c-7.257-17.623-12.958-34.21-18.142-51.833c-18.144-59.476-27.402-121.307-27.472-183.49V877.02 c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z'
|
||||
/>
|
||||
<path
|
||||
opacity='.2'
|
||||
d='M1192.167,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855h-447.84 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1192.167z'
|
||||
/>
|
||||
<path
|
||||
opacity='.2'
|
||||
d='M1140.333,777.5v786.312c-0.395,52.223-42.632,94.46-94.855,94.855H649.472 c-18.144-59.476-27.402-121.307-27.472-183.49V877.02c-1.246-53.659,41.198-98.19,94.855-99.52H1140.333z'
|
||||
/>
|
||||
<path
|
||||
opacity='.1'
|
||||
d='M1244,509.522v163.275c-8.812,0.518-17.105,1.037-25.917,1.037 c-8.812,0-17.105-0.518-25.917-1.037c-17.496-1.161-34.848-3.937-51.833-8.293c-104.963-24.857-191.679-98.469-233.25-198.003 c-7.153-16.715-12.706-34.071-16.587-51.833h258.648C1201.449,414.866,1243.801,457.217,1244,509.522z'
|
||||
/>
|
||||
<path
|
||||
opacity='.2'
|
||||
d='M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z'
|
||||
/>
|
||||
<path
|
||||
opacity='.2'
|
||||
d='M1192.167,561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293 c-104.963-24.857-191.679-98.469-233.25-198.003h190.228C1149.616,466.699,1191.968,509.051,1192.167,561.355z'
|
||||
/>
|
||||
<path
|
||||
opacity='.2'
|
||||
d='M1140.333,561.355v103.148c-104.963-24.857-191.679-98.469-233.25-198.003 h138.395C1097.783,466.699,1140.134,509.051,1140.333,561.355z'
|
||||
/>
|
||||
<linearGradient
|
||||
id='a'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
x1='198.099'
|
||||
y1='1683.0726'
|
||||
x2='942.2344'
|
||||
y2='394.2607'
|
||||
gradientTransform='matrix(1 0 0 -1 0 2075.3333)'
|
||||
>
|
||||
<stop offset='0' stop-color='#5a62c3' />
|
||||
<stop offset='.5' stop-color='#4d55bd' />
|
||||
<stop offset='1' stop-color='#3940ab' />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill='url(#a)'
|
||||
d='M95.01,466.5h950.312c52.473,0,95.01,42.538,95.01,95.01v950.312c0,52.473-42.538,95.01-95.01,95.01 H95.01c-52.473,0-95.01-42.538-95.01-95.01V561.51C0,509.038,42.538,466.5,95.01,466.5z'
|
||||
/>
|
||||
<path
|
||||
fill='#FFF'
|
||||
d='M820.211,828.193H630.241v517.297H509.211V828.193H320.123V727.844h500.088V828.193z'
|
||||
/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
@@ -52,6 +92,7 @@ With Microsoft Teams, you can:
|
||||
In Sim Studio, the Microsoft Teams integration enables your agents to interact directly with chat messages programmatically. This allows for powerful automation scenarios such as sending updates, posting alerts, coordinating tasks, and responding to conversations in real time. Your agents can write new messages to chats or channels, update content based on workflow data, and engage with users where collaboration happens. By integrating Sim Studio with Microsoft Teams, you bridge the gap between intelligent workflows and team communication — empowering your agents to streamline collaboration, automate communication tasks, and keep your teams aligned.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Microsoft Teams functionality to manage messages. Read content from existing messages and write to messages using OAuth authentication. Supports text content manipulation for message creation and editing.
|
||||
|
||||
166
apps/docs/content/docs/tools/outlook.mdx
Normal file
166
apps/docs/content/docs/tools/outlook.mdx
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Outlook
|
||||
description: Access Outlook
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="outlook"
|
||||
color="#E0E0E0"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
id="Livello_1"
|
||||
x="0px" y="0px"
|
||||
viewBox="0 0 1831.085 1703.335"
|
||||
enable-background="new 0 0 1831.085 1703.335"
|
||||
>
|
||||
<path fill="#0A2767" d="M1831.083,894.25c0.1-14.318-7.298-27.644-19.503-35.131h-0.213l-0.767-0.426l-634.492-375.585 c-2.74-1.851-5.583-3.543-8.517-5.067c-24.498-12.639-53.599-12.639-78.098,0c-2.934,1.525-5.777,3.216-8.517,5.067L446.486,858.693 l-0.766,0.426c-19.392,12.059-25.337,37.556-13.278,56.948c3.553,5.714,8.447,10.474,14.257,13.868l634.492,375.585 c2.749,1.835,5.592,3.527,8.517,5.068c24.498,12.639,53.599,12.639,78.098,0c2.925-1.541,5.767-3.232,8.517-5.068l634.492-375.585 C1823.49,922.545,1831.228,908.923,1831.083,894.25z"/>
|
||||
<path fill="#0364B8" d="M520.453,643.477h416.38v381.674h-416.38V643.477z M1745.917,255.5V80.908 c1-43.652-33.552-79.862-77.203-80.908H588.204C544.552,1.046,510,37.256,511,80.908V255.5l638.75,170.333L1745.917,255.5z"/>
|
||||
<path fill="#0078D4" d="M511,255.5h425.833v383.25H511V255.5z"/>
|
||||
<path fill="#28A8EA" d="M1362.667,255.5H936.833v383.25L1362.667,1022h383.25V638.75L1362.667,255.5z"/>
|
||||
<path fill="#0078D4" d="M936.833,638.75h425.833V1022H936.833V638.75z"/>
|
||||
<path fill="#0364B8" d="M936.833,1022h425.833v383.25H936.833V1022z"/>
|
||||
<path fill="#14447D" d="M520.453,1025.151h416.38v346.969h-416.38V1025.151z"/>
|
||||
<path fill="#0078D4" d="M1362.667,1022h383.25v383.25h-383.25V1022z"/>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="1128.4584" y1="811.0833" x2="1128.4584" y2="1.9982" gradientTransform="matrix(1 0 0 -1 0 1705.3334)">
|
||||
<stop offset="0" style={{ stopColor: '#35B8F1' }} />
|
||||
<stop offset="1" style={{ stopColor: '#28A8EA' }} />
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M1811.58,927.593l-0.809,0.426l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-10.777,5.132-22.481,8.029-34.407,8.517l-34.663-20.27c-2.929-1.47-5.773-3.105-8.517-4.897L447.167,906.003h-0.298 l-21.036-11.753v722.384c0.328,48.196,39.653,87.006,87.849,86.7h1230.914c0.724,0,1.363-0.341,2.129-0.341 c10.18-0.651,20.216-2.745,29.808-6.217c4.145-1.756,8.146-3.835,11.966-6.217c2.853-1.618,7.75-5.152,7.75-5.152 c21.814-16.142,34.726-41.635,34.833-68.772V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z"/>
|
||||
<path opacity="0.5" fill="#0A2767" enable-background="new " d="M1797.017,891.397v44.287l-663.448,456.791L446.699,906.301 c0-0.235-0.191-0.426-0.426-0.426l0,0l-63.023-37.899v-31.938l25.976-0.426l54.932,31.512l1.277,0.426l4.684,2.981 c0,0,645.563,368.346,647.267,369.197l24.698,14.478c2.129-0.852,4.258-1.703,6.813-2.555 c1.278-0.852,640.879-360.681,640.879-360.681L1797.017,891.397z"/>
|
||||
<path fill="#1490DF" d="M1811.58,927.593l-0.809,0.468l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-24.641,12.038-53.457,12.038-78.098,0c-2.918-1.445-5.76-3.037-8.517-4.769L446.657,928.061l-0.766-0.468 c-12.25-6.642-19.93-19.409-20.057-33.343v722.384c0.305,48.188,39.616,87.004,87.803,86.7c0.001,0,0.002,0,0.004,0h1229.636 c48.188,0.307,87.5-38.509,87.807-86.696c0-0.001,0-0.002,0-0.004V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z"/>
|
||||
<path opacity="0.1" enable-background="new " d="M1185.52,1279.629l-9.496,5.323c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l241.405,285.479l421.107,101.476c11.539-8.716,20.717-20.178,26.7-33.343L1185.52,1279.629 z"/>
|
||||
<path opacity="0.05" enable-background="new " d="M1228.529,1255.442l-52.505,29.51c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l113.101,311.838l549.538,74.989c21.649-16.254,34.394-41.743,34.407-68.815v-9.326 L1228.529,1255.442z"/>
|
||||
<path fill="#28A8EA" d="M514.833,1703.333h1228.316c18.901,0.096,37.335-5.874,52.59-17.033l-697.089-408.331 c-2.929-1.47-5.773-3.105-8.517-4.897L447.125,906.088h-0.298l-20.993-11.838v719.914 C425.786,1663.364,465.632,1703.286,514.833,1703.333C514.832,1703.333,514.832,1703.333,514.833,1703.333z"/>
|
||||
<path opacity="0.1" enable-background="new " d="M1022,418.722v908.303c-0.076,31.846-19.44,60.471-48.971,72.392 c-9.148,3.931-19,5.96-28.957,5.962H425.833V383.25H511v-42.583h433.073C987.092,340.83,1021.907,375.702,1022,418.722z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M979.417,461.305v908.302c0.107,10.287-2.074,20.469-6.388,29.808 c-11.826,29.149-40.083,48.273-71.54,48.417H425.833V383.25h475.656c12.356-0.124,24.533,2.958,35.344,8.943 C962.937,405.344,979.407,432.076,979.417,461.305z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M979.417,461.305v823.136c-0.208,43-34.928,77.853-77.927,78.225H425.833V383.25 h475.656c12.356-0.124,24.533,2.958,35.344,8.943C962.937,405.344,979.407,432.076,979.417,461.305z"/>
|
||||
<path opacity="0.2" enable-background="new " d="M936.833,461.305v823.136c-0.046,43.067-34.861,78.015-77.927,78.225H425.833 V383.25h433.072c43.062,0.023,77.951,34.951,77.927,78.013C936.833,461.277,936.833,461.291,936.833,461.305z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="162.7469" y1="1383.0741" x2="774.0864" y2="324.2592" gradientTransform="matrix(1 0 0 -1 0 1705.3334)">
|
||||
<stop offset="0" style={{ stopColor: '#1784D9' }} />
|
||||
<stop offset="0.5" style={{ stopColor: '#107AD5' }} />
|
||||
<stop offset="1" style={{ stopColor: '#0A63C9' }} />
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M78.055,383.25h780.723c43.109,0,78.055,34.947,78.055,78.055v780.723 c0,43.109-34.946,78.055-78.055,78.055H78.055c-43.109,0-78.055-34.947-78.055-78.055V461.305 C0,418.197,34.947,383.25,78.055,383.25z"/>
|
||||
<path fill="#FFFFFF" d="M243.96,710.631c19.238-40.988,50.29-75.289,89.17-98.495c43.057-24.651,92.081-36.94,141.675-35.515 c45.965-0.997,91.321,10.655,131.114,33.683c37.414,22.312,67.547,55.004,86.742,94.109c20.904,43.09,31.322,90.512,30.405,138.396 c1.013,50.043-9.706,99.628-31.299,144.783c-19.652,40.503-50.741,74.36-89.425,97.388c-41.327,23.734-88.367,35.692-136.011,34.578 c-46.947,1.133-93.303-10.651-134.01-34.067c-37.738-22.341-68.249-55.07-87.892-94.28c-21.028-42.467-31.57-89.355-30.745-136.735 C212.808,804.859,223.158,755.686,243.96,710.631z M339.006,941.858c10.257,25.912,27.651,48.385,50.163,64.812 c22.93,16.026,50.387,24.294,78.353,23.591c29.783,1.178,59.14-7.372,83.634-24.358c22.227-16.375,39.164-38.909,48.715-64.812 c10.677-28.928,15.946-59.572,15.543-90.404c0.33-31.127-4.623-62.084-14.649-91.554c-8.855-26.607-25.246-50.069-47.182-67.537 c-23.88-17.79-53.158-26.813-82.91-25.55c-28.572-0.74-56.644,7.593-80.184,23.804c-22.893,16.496-40.617,39.168-51.1,65.365 c-23.255,60.049-23.376,126.595-0.341,186.728L339.006,941.858z"/>
|
||||
<path fill="#50D9FF" d="M1362.667,255.5h383.25v383.25h-383.25V255.5z"/>
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Microsoft Outlook](https://outlook.office365.com) is a comprehensive email and calendar platform that helps users manage communications, schedules, and tasks efficiently. As part of Microsoft's productivity suite, Outlook offers robust tools for sending and organizing emails, coordinating meetings, and integrating seamlessly with Microsoft 365 applications — enabling individuals and teams to stay organized and connected across devices.
|
||||
|
||||
With Microsoft Outlook, you can:
|
||||
|
||||
- **Send and receive emails**: Communicate clearly and professionally with individuals or distribution lists
|
||||
- **Manage calendars and events**: Schedule meetings, set reminders, and view availability
|
||||
- **Organize your inbox**: Use folders, categories, and rules to keep your email streamlined
|
||||
- **Access contacts and tasks**: Keep track of key people and action items in one place
|
||||
- **Integrate with Microsoft 365**: Work seamlessly with Word, Excel, Teams, and other Microsoft apps
|
||||
- **Access across devices**: Use Outlook on desktop, web, and mobile with real-time sync
|
||||
- **Maintain privacy and security**: Leverage enterprise-grade encryption and compliance controls
|
||||
|
||||
In Sim Studio, the Microsoft Outlook integration enables your agents to interact directly with email and calendar data programmatically. This allows for powerful automation scenarios such as sending custom email updates, parsing incoming messages for workflow triggers, creating calendar events, and managing task reminders. By connecting Sim Studio with Microsoft Outlook, you enable intelligent agents to automate communications, streamline scheduling, and maintain visibility into organizational correspondence — all within your workflow ecosystem.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Outlook functionality to read, draft, and send email messages within your workflow. Automate email communications and process email content using OAuth authentication.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `outlook_send`
|
||||
|
||||
Send emails using Outlook
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Access token for Outlook API |
|
||||
| `to` | string | Yes | Recipient email address |
|
||||
| `subject` | string | Yes | Email subject |
|
||||
| `body` | string | Yes | Email body content |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `message` | string |
|
||||
| `results` | string |
|
||||
| `timestamp` | string |
|
||||
|
||||
### `outlook_draft`
|
||||
|
||||
Draft emails using Outlook
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | Access token for Outlook API |
|
||||
| `to` | string | Yes | Recipient email address |
|
||||
| `subject` | string | Yes | Email subject |
|
||||
| `body` | string | Yes | Email body content |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `message` | string |
|
||||
| `results` | string |
|
||||
| `subject` | string |
|
||||
| `status` | string |
|
||||
| `timestamp` | string |
|
||||
|
||||
### `outlook_read`
|
||||
|
||||
Read emails from Outlook
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `accessToken` | string | Yes | OAuth access token for Outlook |
|
||||
| `folder` | string | No | Folder ID to read emails from \(default: Inbox\) |
|
||||
| `maxResults` | number | No | Maximum number of emails to retrieve \(default: 1, max: 10\) |
|
||||
| `messageId` | string | No | Message ID to read |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| --------- | ---- |
|
||||
| `message` | string |
|
||||
| `results` | json |
|
||||
|
||||
|
||||
|
||||
## Block Configuration
|
||||
|
||||
### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `operation` | string | Yes | Operation |
|
||||
|
||||
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
| ------ | ---- | ----------- |
|
||||
| `response` | object | Output from response |
|
||||
| ↳ `message` | string | message of the response |
|
||||
| ↳ `results` | json | results of the response |
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `outlook`
|
||||
132
apps/sim/app/api/auth/oauth/outlook/folders/route.ts
Normal file
132
apps/sim/app/api/auth/oauth/outlook/folders/route.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { refreshAccessTokenIfNeeded } from '../../utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('OutlookFoldersAPI')
|
||||
|
||||
interface OutlookFolder {
|
||||
id: string
|
||||
displayName: string
|
||||
totalItemCount?: number
|
||||
unreadItemCount?: number
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const session = await getSession()
|
||||
const { searchParams } = new URL(request.url)
|
||||
const credentialId = searchParams.get('credentialId')
|
||||
|
||||
if (!credentialId) {
|
||||
logger.error('Missing credentialId in request')
|
||||
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the userId from the session
|
||||
const userId = session?.user?.id || ''
|
||||
|
||||
if (!userId) {
|
||||
logger.error('No user ID found in session')
|
||||
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
|
||||
}
|
||||
|
||||
const accessToken = await refreshAccessTokenIfNeeded(
|
||||
credentialId,
|
||||
userId,
|
||||
crypto.randomUUID().slice(0, 8)
|
||||
)
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Failed to get access token', { credentialId, userId })
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Could not retrieve access token',
|
||||
authRequired: true,
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const response = await fetch('https://graph.microsoft.com/v1.0/me/mailFolders', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
logger.error('Microsoft Graph API error getting folders', {
|
||||
status: response.status,
|
||||
error: errorData,
|
||||
endpoint: 'https://graph.microsoft.com/v1.0/me/mailFolders',
|
||||
})
|
||||
|
||||
// Check for auth errors specifically
|
||||
if (response.status === 401) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Authentication failed. Please reconnect your Outlook account.',
|
||||
authRequired: true,
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const folders = data.value || []
|
||||
|
||||
// Transform folders to match the expected format
|
||||
const transformedFolders = folders.map((folder: OutlookFolder) => ({
|
||||
id: folder.id,
|
||||
name: folder.displayName,
|
||||
type: 'folder',
|
||||
messagesTotal: folder.totalItemCount || 0,
|
||||
messagesUnread: folder.unreadItemCount || 0,
|
||||
}))
|
||||
|
||||
return NextResponse.json({
|
||||
folders: transformedFolders,
|
||||
})
|
||||
} catch (innerError) {
|
||||
logger.error('Error during API requests:', innerError)
|
||||
|
||||
// Check if it's an authentication error
|
||||
const errorMessage = innerError instanceof Error ? innerError.message : String(innerError)
|
||||
if (
|
||||
errorMessage.includes('auth') ||
|
||||
errorMessage.includes('token') ||
|
||||
errorMessage.includes('unauthorized') ||
|
||||
errorMessage.includes('unauthenticated')
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Authentication failed. Please reconnect your Outlook account.',
|
||||
authRequired: true,
|
||||
details: errorMessage,
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
throw innerError
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error processing Outlook folders request:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to retrieve Outlook folders',
|
||||
details: (error as Error).message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,10 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'Group.Read.All': 'Read your Microsoft groups',
|
||||
'Group.ReadWrite.All': 'Write to your Microsoft groups',
|
||||
'Team.ReadBasic.All': 'Read your Microsoft teams',
|
||||
'Mail.ReadWrite': 'Write to your Microsoft emails',
|
||||
'Mail.ReadBasic': 'Read your Microsoft emails',
|
||||
'Mail.Read': 'Read your Microsoft emails',
|
||||
'Mail.Send': 'Send emails on your behalf',
|
||||
identify: 'Read your Discord user',
|
||||
bot: 'Read your Discord bot',
|
||||
'messages.read': 'Read your Discord messages',
|
||||
|
||||
@@ -168,7 +168,6 @@ export function CredentialSelector({
|
||||
if (!baseProviderConfig) {
|
||||
return <ExternalLink className='h-4 w-4' />
|
||||
}
|
||||
|
||||
// Always use the base provider icon for a more consistent UI
|
||||
return baseProviderConfig.icon({ className: 'h-4 w-4' })
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Check, ChevronDown, RefreshCw } from 'lucide-react'
|
||||
import { GmailIcon } from '@/components/icons'
|
||||
import { GmailIcon, OutlookIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Command,
|
||||
@@ -112,7 +112,7 @@ export function FolderSelector({
|
||||
// Fetch a single folder by ID when we have a selectedFolderId but no metadata
|
||||
const fetchFolderById = useCallback(
|
||||
async (folderId: string) => {
|
||||
if (!selectedCredentialId || !folderId) return null
|
||||
if (!selectedCredentialId || !folderId || provider === 'outlook') return null
|
||||
|
||||
setIsLoadingSelectedFolder(true)
|
||||
try {
|
||||
@@ -144,10 +144,10 @@ export function FolderSelector({
|
||||
setIsLoadingSelectedFolder(false)
|
||||
}
|
||||
},
|
||||
[selectedCredentialId, onFolderInfoChange]
|
||||
[selectedCredentialId, onFolderInfoChange, provider]
|
||||
)
|
||||
|
||||
// Fetch folders from Gmail
|
||||
// Fetch folders from Gmail or Outlook
|
||||
const fetchFolders = useCallback(
|
||||
async (searchQuery?: string) => {
|
||||
if (!selectedCredentialId) return
|
||||
@@ -163,22 +163,32 @@ export function FolderSelector({
|
||||
queryParams.append('query', searchQuery)
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/auth/oauth/gmail/labels?${queryParams.toString()}`)
|
||||
// Determine the API endpoint based on provider
|
||||
let apiEndpoint: string
|
||||
if (provider === 'outlook') {
|
||||
apiEndpoint = `/api/auth/oauth/outlook/folders?${queryParams.toString()}`
|
||||
} else {
|
||||
// Default to Gmail
|
||||
apiEndpoint = `/api/auth/oauth/gmail/labels?${queryParams.toString()}`
|
||||
}
|
||||
|
||||
const response = await fetch(apiEndpoint)
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setFolders(data.labels || [])
|
||||
const folderList = provider === 'outlook' ? data.folders : data.labels
|
||||
setFolders(folderList || [])
|
||||
|
||||
// If we have a selected folder ID, find the folder info
|
||||
if (selectedFolderId) {
|
||||
const folderInfo = data.labels.find(
|
||||
const folderInfo = folderList.find(
|
||||
(folder: FolderInfo) => folder.id === selectedFolderId
|
||||
)
|
||||
if (folderInfo) {
|
||||
setSelectedFolder(folderInfo)
|
||||
onFolderInfoChange?.(folderInfo)
|
||||
} else if (!searchQuery) {
|
||||
// Only try to fetch by ID if this is not a search query
|
||||
} else if (!searchQuery && provider !== 'outlook') {
|
||||
// Only try to fetch by ID for Gmail if this is not a search query
|
||||
// and we couldn't find the folder in the list
|
||||
fetchFolderById(selectedFolderId)
|
||||
}
|
||||
@@ -196,7 +206,7 @@ export function FolderSelector({
|
||||
setIsLoading(false)
|
||||
}
|
||||
},
|
||||
[selectedCredentialId, selectedFolderId, onFolderInfoChange, fetchFolderById]
|
||||
[selectedCredentialId, selectedFolderId, onFolderInfoChange, fetchFolderById, provider]
|
||||
)
|
||||
|
||||
// Fetch credentials on initial mount
|
||||
@@ -221,12 +231,12 @@ export function FolderSelector({
|
||||
}
|
||||
}, [value])
|
||||
|
||||
// Fetch the selected folder metadata once credentials are ready
|
||||
// Fetch the selected folder metadata once credentials are ready (Gmail only)
|
||||
useEffect(() => {
|
||||
if (value && selectedCredentialId && !selectedFolder) {
|
||||
if (value && selectedCredentialId && !selectedFolder && provider !== 'outlook') {
|
||||
fetchFolderById(value)
|
||||
}
|
||||
}, [value, selectedCredentialId, selectedFolder, fetchFolderById])
|
||||
}, [value, selectedCredentialId, selectedFolder, fetchFolderById, provider])
|
||||
|
||||
// Handle folder selection
|
||||
const handleSelectFolder = (folder: FolderInfo) => {
|
||||
@@ -263,7 +273,23 @@ export function FolderSelector({
|
||||
|
||||
const getFolderIcon = (size: 'sm' | 'md' = 'sm') => {
|
||||
const iconSize = size === 'sm' ? 'h-4 w-4' : 'h-5 w-5'
|
||||
return <GmailIcon className={iconSize} />
|
||||
if (provider === 'gmail') {
|
||||
return <GmailIcon className={iconSize} />
|
||||
}
|
||||
if (provider === 'outlook') {
|
||||
return <OutlookIcon className={iconSize} />
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const getProviderName = () => {
|
||||
if (provider === 'outlook') return 'Outlook'
|
||||
return 'Gmail'
|
||||
}
|
||||
|
||||
const getFolderLabel = () => {
|
||||
if (provider === 'outlook') return 'folders'
|
||||
return 'labels'
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -283,11 +309,6 @@ export function FolderSelector({
|
||||
{getFolderIcon('sm')}
|
||||
<span className='truncate font-normal'>{selectedFolder.name}</span>
|
||||
</div>
|
||||
) : selectedFolderId && (isLoadingSelectedFolder || !selectedCredentialId) ? (
|
||||
<div className='flex items-center gap-2'>
|
||||
<RefreshCw className='h-4 w-4 animate-spin' />
|
||||
<span className='text-muted-foreground'>Loading label...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex items-center gap-2'>
|
||||
{getFolderIcon('sm')}
|
||||
@@ -321,24 +342,27 @@ export function FolderSelector({
|
||||
)}
|
||||
|
||||
<Command>
|
||||
<CommandInput placeholder='Search labels...' onValueChange={handleSearch} />
|
||||
<CommandInput
|
||||
placeholder={`Search ${getFolderLabel()}...`}
|
||||
onValueChange={handleSearch}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
{isLoading ? (
|
||||
<div className='flex items-center justify-center p-4'>
|
||||
<RefreshCw className='h-4 w-4 animate-spin' />
|
||||
<span className='ml-2'>Loading labels...</span>
|
||||
<span className='ml-2'>Loading {getFolderLabel()}...</span>
|
||||
</div>
|
||||
) : credentials.length === 0 ? (
|
||||
<div className='p-4 text-center'>
|
||||
<p className='font-medium text-sm'>No accounts connected.</p>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
Connect a Gmail account to continue.
|
||||
Connect a {getProviderName()} account to continue.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className='p-4 text-center'>
|
||||
<p className='font-medium text-sm'>No labels found.</p>
|
||||
<p className='font-medium text-sm'>No {getFolderLabel()} found.</p>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
Try a different search or account.
|
||||
</p>
|
||||
@@ -371,7 +395,7 @@ export function FolderSelector({
|
||||
{folders.length > 0 && (
|
||||
<CommandGroup>
|
||||
<div className='px-2 py-1.5 font-medium text-muted-foreground text-xs'>
|
||||
Labels
|
||||
{getFolderLabel().charAt(0).toUpperCase() + getFolderLabel().slice(1)}
|
||||
</div>
|
||||
{folders.map((folder) => (
|
||||
<CommandItem
|
||||
@@ -394,22 +418,11 @@ export function FolderSelector({
|
||||
<CommandGroup>
|
||||
<CommandItem onSelect={handleAddCredential}>
|
||||
<div className='flex items-center gap-2 text-primary'>
|
||||
<span>Connect Gmail account</span>
|
||||
<span>Connect {getProviderName()} account</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
{/* Add another account option */}
|
||||
{/* {credentials.length > 0 && (
|
||||
<CommandGroup>
|
||||
<CommandItem onSelect={handleAddCredential}>
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<span>Connect Another Account</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)} */}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
@@ -421,7 +434,7 @@ export function FolderSelector({
|
||||
isOpen={showOAuthModal}
|
||||
onClose={() => setShowOAuthModal(false)}
|
||||
provider={provider}
|
||||
toolName='Gmail'
|
||||
toolName={getProviderName()}
|
||||
requiredScopes={requiredScopes}
|
||||
serviceId={getServiceId()}
|
||||
/>
|
||||
|
||||
150
apps/sim/blocks/blocks/outlook.ts
Normal file
150
apps/sim/blocks/blocks/outlook.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { OutlookIcon } from '@/components/icons'
|
||||
import type {
|
||||
OutlookDraftResponse,
|
||||
OutlookReadResponse,
|
||||
OutlookSendResponse,
|
||||
} from '@/tools/outlook/types'
|
||||
import type { BlockConfig } from '../types'
|
||||
|
||||
export const OutlookBlock: BlockConfig<
|
||||
OutlookReadResponse | OutlookSendResponse | OutlookDraftResponse
|
||||
> = {
|
||||
type: 'outlook',
|
||||
name: 'Outlook',
|
||||
description: 'Access Outlook',
|
||||
longDescription:
|
||||
'Integrate Outlook functionality to read, draft, andsend email messages within your workflow. Automate email communications and process email content using OAuth authentication.',
|
||||
docsLink: 'https://docs.simstudio.ai/tools/outlook',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: OutlookIcon,
|
||||
subBlocks: [
|
||||
// Operation selector
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Send Email', id: 'send_outlook' },
|
||||
{ label: 'Draft Email', id: 'draft_outlook' },
|
||||
{ label: 'Read Email', id: 'read_outlook' },
|
||||
],
|
||||
},
|
||||
// Gmail Credentials
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Microsoft Account',
|
||||
type: 'oauth-input',
|
||||
layout: 'full',
|
||||
provider: 'outlook',
|
||||
serviceId: 'outlook',
|
||||
requiredScopes: [
|
||||
'Mail.ReadWrite',
|
||||
'Mail.ReadBasic',
|
||||
'Mail.Read',
|
||||
'Mail.Send',
|
||||
'offline_access',
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
],
|
||||
placeholder: 'Select Microsoft account',
|
||||
},
|
||||
// Send Email Fields
|
||||
{
|
||||
id: 'to',
|
||||
title: 'To',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Recipient email address',
|
||||
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
|
||||
},
|
||||
{
|
||||
id: 'subject',
|
||||
title: 'Subject',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Email subject',
|
||||
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
|
||||
},
|
||||
{
|
||||
id: 'body',
|
||||
title: 'Body',
|
||||
type: 'long-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Email content',
|
||||
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
|
||||
},
|
||||
// Read Email Fields - Add folder selector
|
||||
{
|
||||
id: 'folder',
|
||||
title: 'Folder',
|
||||
type: 'folder-selector',
|
||||
layout: 'full',
|
||||
provider: 'outlook',
|
||||
serviceId: 'outlook',
|
||||
requiredScopes: ['Mail.ReadWrite', 'Mail.ReadBasic', 'Mail.Read'],
|
||||
placeholder: 'Select Outlook folder',
|
||||
condition: { field: 'operation', value: 'read_outlook' },
|
||||
},
|
||||
{
|
||||
id: 'maxResults',
|
||||
title: 'Number of Emails',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Number of emails to retrieve (default: 1, max: 10)',
|
||||
condition: { field: 'operation', value: 'read_outlook' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: ['outlook_send', 'outlook_draft', 'outlook_read'],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'send_outlook':
|
||||
return 'outlook_send'
|
||||
case 'read_outlook':
|
||||
return 'outlook_read'
|
||||
case 'draft_outlook':
|
||||
return 'outlook_draft'
|
||||
default:
|
||||
throw new Error(`Invalid Outlook operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
// Pass the credential directly from the credential field
|
||||
const { credential, ...rest } = params
|
||||
|
||||
// Set default folder to INBOX if not specified
|
||||
if (rest.operation === 'read_outlook' && !rest.folder) {
|
||||
rest.folder = 'INBOX'
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
credential, // Keep the credential parameter
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', required: true },
|
||||
credential: { type: 'string', required: true },
|
||||
// Send operation inputs
|
||||
to: { type: 'string', required: false },
|
||||
subject: { type: 'string', required: false },
|
||||
body: { type: 'string', required: false },
|
||||
// Read operation inputs
|
||||
folder: { type: 'string', required: false },
|
||||
maxResults: { type: 'number', required: false },
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
type: {
|
||||
message: 'string',
|
||||
results: 'json',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import { MicrosoftTeamsBlock } from './blocks/microsoft_teams'
|
||||
import { MistralParseBlock } from './blocks/mistral_parse'
|
||||
import { NotionBlock } from './blocks/notion'
|
||||
import { OpenAIBlock } from './blocks/openai'
|
||||
import { OutlookBlock } from './blocks/outlook'
|
||||
import { PerplexityBlock } from './blocks/perplexity'
|
||||
import { PineconeBlock } from './blocks/pinecone'
|
||||
import { RedditBlock } from './blocks/reddit'
|
||||
@@ -92,6 +93,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
mistral_parse: MistralParseBlock,
|
||||
notion: NotionBlock,
|
||||
openai: OpenAIBlock,
|
||||
outlook: OutlookBlock,
|
||||
perplexity: PerplexityBlock,
|
||||
pinecone: PineconeBlock,
|
||||
reddit: RedditBlock,
|
||||
|
||||
@@ -2556,3 +2556,118 @@ export function MicrosoftTeamsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function OutlookIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
version='1.1'
|
||||
id='Livello_1'
|
||||
x='0px'
|
||||
y='0px'
|
||||
viewBox='0 0 1831.085 1703.335'
|
||||
enable-background='new 0 0 1831.085 1703.335'
|
||||
>
|
||||
<path
|
||||
fill='#0A2767'
|
||||
d='M1831.083,894.25c0.1-14.318-7.298-27.644-19.503-35.131h-0.213l-0.767-0.426l-634.492-375.585 c-2.74-1.851-5.583-3.543-8.517-5.067c-24.498-12.639-53.599-12.639-78.098,0c-2.934,1.525-5.777,3.216-8.517,5.067L446.486,858.693 l-0.766,0.426c-19.392,12.059-25.337,37.556-13.278,56.948c3.553,5.714,8.447,10.474,14.257,13.868l634.492,375.585 c2.749,1.835,5.592,3.527,8.517,5.068c24.498,12.639,53.599,12.639,78.098,0c2.925-1.541,5.767-3.232,8.517-5.068l634.492-375.585 C1823.49,922.545,1831.228,908.923,1831.083,894.25z'
|
||||
/>
|
||||
<path
|
||||
fill='#0364B8'
|
||||
d='M520.453,643.477h416.38v381.674h-416.38V643.477z M1745.917,255.5V80.908 c1-43.652-33.552-79.862-77.203-80.908H588.204C544.552,1.046,510,37.256,511,80.908V255.5l638.75,170.333L1745.917,255.5z'
|
||||
/>
|
||||
<path fill='#0078D4' d='M511,255.5h425.833v383.25H511V255.5z' />
|
||||
<path
|
||||
fill='#28A8EA'
|
||||
d='M1362.667,255.5H936.833v383.25L1362.667,1022h383.25V638.75L1362.667,255.5z'
|
||||
/>
|
||||
<path fill='#0078D4' d='M936.833,638.75h425.833V1022H936.833V638.75z' />
|
||||
<path fill='#0364B8' d='M936.833,1022h425.833v383.25H936.833V1022z' />
|
||||
<path fill='#14447D' d='M520.453,1025.151h416.38v346.969h-416.38V1025.151z' />
|
||||
<path fill='#0078D4' d='M1362.667,1022h383.25v383.25h-383.25V1022z' />
|
||||
<linearGradient
|
||||
id='SVGID_1_'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
x1='1128.4584'
|
||||
y1='811.0833'
|
||||
x2='1128.4584'
|
||||
y2='1.9982'
|
||||
gradientTransform='matrix(1 0 0 -1 0 1705.3334)'
|
||||
>
|
||||
<stop offset='0' style={{ stopColor: '#35B8F1' }} />
|
||||
<stop offset='1' style={{ stopColor: '#28A8EA' }} />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill='url(#SVGID_1_)'
|
||||
d='M1811.58,927.593l-0.809,0.426l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-10.777,5.132-22.481,8.029-34.407,8.517l-34.663-20.27c-2.929-1.47-5.773-3.105-8.517-4.897L447.167,906.003h-0.298 l-21.036-11.753v722.384c0.328,48.196,39.653,87.006,87.849,86.7h1230.914c0.724,0,1.363-0.341,2.129-0.341 c10.18-0.651,20.216-2.745,29.808-6.217c4.145-1.756,8.146-3.835,11.966-6.217c2.853-1.618,7.75-5.152,7.75-5.152 c21.814-16.142,34.726-41.635,34.833-68.772V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.5'
|
||||
fill='#0A2767'
|
||||
enable-background='new '
|
||||
d='M1797.017,891.397v44.287l-663.448,456.791L446.699,906.301 c0-0.235-0.191-0.426-0.426-0.426l0,0l-63.023-37.899v-31.938l25.976-0.426l54.932,31.512l1.277,0.426l4.684,2.981 c0,0,645.563,368.346,647.267,369.197l24.698,14.478c2.129-0.852,4.258-1.703,6.813-2.555 c1.278-0.852,640.879-360.681,640.879-360.681L1797.017,891.397z'
|
||||
/>
|
||||
<path
|
||||
fill='#1490DF'
|
||||
d='M1811.58,927.593l-0.809,0.468l-634.492,356.848c-2.768,1.703-5.578,3.321-8.517,4.769 c-24.641,12.038-53.457,12.038-78.098,0c-2.918-1.445-5.76-3.037-8.517-4.769L446.657,928.061l-0.766-0.468 c-12.25-6.642-19.93-19.409-20.057-33.343v722.384c0.305,48.188,39.616,87.004,87.803,86.7c0.001,0,0.002,0,0.004,0h1229.636 c48.188,0.307,87.5-38.509,87.807-86.696c0-0.001,0-0.002,0-0.004V894.25C1831.068,908.067,1823.616,920.807,1811.58,927.593z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.1'
|
||||
enable-background='new '
|
||||
d='M1185.52,1279.629l-9.496,5.323c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l241.405,285.479l421.107,101.476c11.539-8.716,20.717-20.178,26.7-33.343L1185.52,1279.629 z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.05'
|
||||
enable-background='new '
|
||||
d='M1228.529,1255.442l-52.505,29.51c-2.752,1.752-5.595,3.359-8.517,4.812 c-10.462,5.135-21.838,8.146-33.47,8.857l113.101,311.838l549.538,74.989c21.649-16.254,34.394-41.743,34.407-68.815v-9.326 L1228.529,1255.442z'
|
||||
/>
|
||||
<path
|
||||
fill='#28A8EA'
|
||||
d='M514.833,1703.333h1228.316c18.901,0.096,37.335-5.874,52.59-17.033l-697.089-408.331 c-2.929-1.47-5.773-3.105-8.517-4.897L447.125,906.088h-0.298l-20.993-11.838v719.914 C425.786,1663.364,465.632,1703.286,514.833,1703.333C514.832,1703.333,514.832,1703.333,514.833,1703.333z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.1'
|
||||
enable-background='new '
|
||||
d='M1022,418.722v908.303c-0.076,31.846-19.44,60.471-48.971,72.392 c-9.148,3.931-19,5.96-28.957,5.962H425.833V383.25H511v-42.583h433.073C987.092,340.83,1021.907,375.702,1022,418.722z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.2'
|
||||
enable-background='new '
|
||||
d='M979.417,461.305v908.302c0.107,10.287-2.074,20.469-6.388,29.808 c-11.826,29.149-40.083,48.273-71.54,48.417H425.833V383.25h475.656c12.356-0.124,24.533,2.958,35.344,8.943 C962.937,405.344,979.407,432.076,979.417,461.305z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.2'
|
||||
enable-background='new '
|
||||
d='M979.417,461.305v823.136c-0.208,43-34.928,77.853-77.927,78.225H425.833V383.25 h475.656c12.356-0.124,24.533,2.958,35.344,8.943C962.937,405.344,979.407,432.076,979.417,461.305z'
|
||||
/>
|
||||
<path
|
||||
opacity='0.2'
|
||||
enable-background='new '
|
||||
d='M936.833,461.305v823.136c-0.046,43.067-34.861,78.015-77.927,78.225H425.833 V383.25h433.072c43.062,0.023,77.951,34.951,77.927,78.013C936.833,461.277,936.833,461.291,936.833,461.305z'
|
||||
/>
|
||||
<linearGradient
|
||||
id='SVGID_2_'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
x1='162.7469'
|
||||
y1='1383.0741'
|
||||
x2='774.0864'
|
||||
y2='324.2592'
|
||||
gradientTransform='matrix(1 0 0 -1 0 1705.3334)'
|
||||
>
|
||||
<stop offset='0' style={{ stopColor: '#1784D9' }} />
|
||||
<stop offset='0.5' style={{ stopColor: '#107AD5' }} />
|
||||
<stop offset='1' style={{ stopColor: '#0A63C9' }} />
|
||||
</linearGradient>
|
||||
<path
|
||||
fill='url(#SVGID_2_)'
|
||||
d='M78.055,383.25h780.723c43.109,0,78.055,34.947,78.055,78.055v780.723 c0,43.109-34.946,78.055-78.055,78.055H78.055c-43.109,0-78.055-34.947-78.055-78.055V461.305 C0,418.197,34.947,383.25,78.055,383.25z'
|
||||
/>
|
||||
<path
|
||||
fill='#FFFFFF'
|
||||
d='M243.96,710.631c19.238-40.988,50.29-75.289,89.17-98.495c43.057-24.651,92.081-36.94,141.675-35.515 c45.965-0.997,91.321,10.655,131.114,33.683c37.414,22.312,67.547,55.004,86.742,94.109c20.904,43.09,31.322,90.512,30.405,138.396 c1.013,50.043-9.706,99.628-31.299,144.783c-19.652,40.503-50.741,74.36-89.425,97.388c-41.327,23.734-88.367,35.692-136.011,34.578 c-46.947,1.133-93.303-10.651-134.01-34.067c-37.738-22.341-68.249-55.07-87.892-94.28c-21.028-42.467-31.57-89.355-30.745-136.735 C212.808,804.859,223.158,755.686,243.96,710.631z M339.006,941.858c10.257,25.912,27.651,48.385,50.163,64.812 c22.93,16.026,50.387,24.294,78.353,23.591c29.783,1.178,59.14-7.372,83.634-24.358c22.227-16.375,39.164-38.909,48.715-64.812 c10.677-28.928,15.946-59.572,15.543-90.404c0.33-31.127-4.623-62.084-14.649-91.554c-8.855-26.607-25.246-50.069-47.182-67.537 c-23.88-17.79-53.158-26.813-82.91-25.55c-28.572-0.74-56.644,7.593-80.184,23.804c-22.893,16.496-40.617,39.168-51.1,65.365 c-23.255,60.049-23.376,126.595-0.341,186.728L339.006,941.858z'
|
||||
/>
|
||||
<path fill='#50D9FF' d='M1362.667,255.5h383.25v383.25h-383.25V255.5z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -397,6 +397,31 @@ export const auth = betterAuth({
|
||||
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/microsoft-teams`,
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'outlook',
|
||||
clientId: env.MICROSOFT_CLIENT_ID as string,
|
||||
clientSecret: env.MICROSOFT_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
||||
userInfoUrl: 'https://graph.microsoft.com/v1.0/me',
|
||||
scopes: [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'Mail.ReadWrite',
|
||||
'Mail.ReadBasic',
|
||||
'Mail.Read',
|
||||
'Mail.Send',
|
||||
'offline_access',
|
||||
],
|
||||
responseType: 'code',
|
||||
accessType: 'offline',
|
||||
authentication: 'basic',
|
||||
prompt: 'consent',
|
||||
pkce: true,
|
||||
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/outlook`,
|
||||
},
|
||||
|
||||
// Supabase provider
|
||||
{
|
||||
providerId: 'supabase',
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
MicrosoftIcon,
|
||||
MicrosoftTeamsIcon,
|
||||
NotionIcon,
|
||||
OutlookIcon,
|
||||
SupabaseIcon,
|
||||
xIcon,
|
||||
} from '@/components/icons'
|
||||
@@ -51,6 +52,7 @@ export type OAuthService =
|
||||
| 'jira'
|
||||
| 'discord'
|
||||
| 'microsoft-teams'
|
||||
| 'outlook'
|
||||
// Define the interface for OAuth provider configuration
|
||||
export interface OAuthProviderConfig {
|
||||
id: OAuthProvider
|
||||
@@ -163,6 +165,24 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
'offline_access',
|
||||
],
|
||||
},
|
||||
outlook: {
|
||||
id: 'outlook',
|
||||
name: 'Outlook',
|
||||
description: 'Connect to Outlook and manage emails.',
|
||||
providerId: 'outlook',
|
||||
icon: (props) => OutlookIcon(props),
|
||||
baseProviderIcon: (props) => MicrosoftIcon(props),
|
||||
scopes: [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'Mail.ReadWrite',
|
||||
'Mail.ReadBasic',
|
||||
'Mail.Read',
|
||||
'Mail.Send',
|
||||
'offline_access',
|
||||
],
|
||||
},
|
||||
},
|
||||
defaultService: 'microsoft',
|
||||
},
|
||||
@@ -356,6 +376,8 @@ export function getServiceIdFromScopes(provider: OAuthProvider, scopes: string[]
|
||||
}
|
||||
} else if (provider === 'microsoft-teams') {
|
||||
return 'microsoft-teams'
|
||||
} else if (provider === 'outlook') {
|
||||
return 'outlook'
|
||||
} else if (provider === 'github') {
|
||||
return 'github'
|
||||
} else if (provider === 'supabase') {
|
||||
@@ -412,6 +434,14 @@ export interface ProviderConfig {
|
||||
* This is a server-safe utility that can be used in both client and server code
|
||||
*/
|
||||
export function parseProvider(provider: OAuthProvider): ProviderConfig {
|
||||
// Handle special cases first
|
||||
if (provider === 'outlook') {
|
||||
return {
|
||||
baseProvider: 'microsoft',
|
||||
featureType: 'outlook',
|
||||
}
|
||||
}
|
||||
|
||||
// Handle compound providers (e.g., 'google-email' -> { baseProvider: 'google', featureType: 'email' })
|
||||
const [base, feature] = provider.split('-')
|
||||
|
||||
@@ -506,6 +536,11 @@ export async function refreshOAuthToken(
|
||||
clientId = env.MICROSOFT_CLIENT_ID
|
||||
clientSecret = env.MICROSOFT_CLIENT_SECRET
|
||||
break
|
||||
case 'outlook':
|
||||
tokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
|
||||
clientId = env.MICROSOFT_CLIENT_ID
|
||||
clientSecret = env.MICROSOFT_CLIENT_SECRET
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${provider}`)
|
||||
}
|
||||
|
||||
112
apps/sim/tools/outlook/draft.ts
Normal file
112
apps/sim/tools/outlook/draft.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
import type { OutlookDraftParams, OutlookDraftResponse } from './types'
|
||||
|
||||
export const outlookDraftTool: ToolConfig<OutlookDraftParams, OutlookDraftResponse> = {
|
||||
id: 'outlook_draft',
|
||||
name: 'Outlook Draft',
|
||||
description: 'Draft emails using Outlook',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'outlook',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Access token for Outlook API',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Recipient email address',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Email subject',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Email body content',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
return `https://graph.microsoft.com/v1.0/me/messages`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
// Validate access token
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params: OutlookDraftParams): Record<string, any> => {
|
||||
return {
|
||||
subject: params.subject,
|
||||
body: {
|
||||
contentType: 'Text',
|
||||
content: params.body,
|
||||
},
|
||||
toRecipients: [
|
||||
{
|
||||
emailAddress: {
|
||||
address: params.to,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
let errorData
|
||||
try {
|
||||
errorData = await response.json()
|
||||
} catch {
|
||||
throw new Error('Failed to draft email')
|
||||
}
|
||||
throw new Error(errorData.error?.message || 'Failed to draft email')
|
||||
}
|
||||
|
||||
// Outlook draft API returns the created message object
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: 'Email drafted successfully',
|
||||
results: {
|
||||
id: data.id,
|
||||
subject: data.subject,
|
||||
status: 'drafted',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
// Handle Outlook API error format
|
||||
if (error.error?.message) {
|
||||
if (error.error.message.includes('invalid authentication credentials')) {
|
||||
return 'Invalid or expired access token. Please reauthenticate.'
|
||||
}
|
||||
if (error.error.message.includes('quota')) {
|
||||
return 'Outlook API quota exceeded. Please try again later.'
|
||||
}
|
||||
return error.error.message
|
||||
}
|
||||
return error.message || 'An unexpected error occurred while drafting email'
|
||||
},
|
||||
}
|
||||
5
apps/sim/tools/outlook/index.ts
Normal file
5
apps/sim/tools/outlook/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { outlookDraftTool } from './draft'
|
||||
import { outlookReadTool } from './read'
|
||||
import { outlookSendTool } from './send'
|
||||
|
||||
export { outlookDraftTool, outlookReadTool, outlookSendTool }
|
||||
146
apps/sim/tools/outlook/read.ts
Normal file
146
apps/sim/tools/outlook/read.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
import type {
|
||||
CleanedOutlookMessage,
|
||||
OutlookMessage,
|
||||
OutlookMessagesResponse,
|
||||
OutlookReadParams,
|
||||
OutlookReadResponse,
|
||||
} from './types'
|
||||
|
||||
export const outlookReadTool: ToolConfig<OutlookReadParams, OutlookReadResponse> = {
|
||||
id: 'outlook_read',
|
||||
name: 'Outlook Read',
|
||||
description: 'Read emails from Outlook',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'outlook',
|
||||
},
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'OAuth access token for Outlook',
|
||||
},
|
||||
folder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Folder ID to read emails from (default: Inbox)',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
description: 'Maximum number of emails to retrieve (default: 1, max: 10)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => {
|
||||
// Set max results (default to 1 for simplicity, max 10) with no negative values
|
||||
const maxResults = params.maxResults
|
||||
? Math.max(1, Math.min(Math.abs(params.maxResults), 10))
|
||||
: 1
|
||||
|
||||
// If folder is provided, read from that specific folder
|
||||
if (params.folder) {
|
||||
return `https://graph.microsoft.com/v1.0/me/mailFolders/${params.folder}/messages?$top=${maxResults}&$orderby=createdDateTime desc`
|
||||
}
|
||||
|
||||
// Otherwise fetch from all messages (default behavior)
|
||||
return `https://graph.microsoft.com/v1.0/me/messages?$top=${maxResults}&$orderby=createdDateTime desc`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
// Validate access token
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Failed to read Outlook mail: ${errorText}`)
|
||||
}
|
||||
|
||||
const data: OutlookMessagesResponse = await response.json()
|
||||
|
||||
// Microsoft Graph API returns messages in a 'value' array
|
||||
const messages = data.value || []
|
||||
|
||||
if (messages.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: 'No mail found.',
|
||||
results: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the message data to only include essential fields
|
||||
const cleanedMessages: CleanedOutlookMessage[] = messages.map((message: OutlookMessage) => ({
|
||||
id: message.id,
|
||||
subject: message.subject,
|
||||
bodyPreview: message.bodyPreview,
|
||||
body: {
|
||||
contentType: message.body?.contentType,
|
||||
content: message.body?.content,
|
||||
},
|
||||
sender: {
|
||||
name: message.sender?.emailAddress?.name,
|
||||
address: message.sender?.emailAddress?.address,
|
||||
},
|
||||
from: {
|
||||
name: message.from?.emailAddress?.name,
|
||||
address: message.from?.emailAddress?.address,
|
||||
},
|
||||
toRecipients:
|
||||
message.toRecipients?.map((recipient) => ({
|
||||
name: recipient.emailAddress?.name,
|
||||
address: recipient.emailAddress?.address,
|
||||
})) || [],
|
||||
ccRecipients:
|
||||
message.ccRecipients?.map((recipient) => ({
|
||||
name: recipient.emailAddress?.name,
|
||||
address: recipient.emailAddress?.address,
|
||||
})) || [],
|
||||
receivedDateTime: message.receivedDateTime,
|
||||
sentDateTime: message.sentDateTime,
|
||||
hasAttachments: message.hasAttachments,
|
||||
isRead: message.isRead,
|
||||
importance: message.importance,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: `Successfully read ${cleanedMessages.length} email(s).`,
|
||||
results: cleanedMessages,
|
||||
},
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
// If it's an Error instance with a message, use that
|
||||
if (error instanceof Error) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
// If it's an object with an error or message property
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
if (error.error) {
|
||||
return typeof error.error === 'string' ? error.error : JSON.stringify(error.error)
|
||||
}
|
||||
if (error.message) {
|
||||
return error.message
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback message
|
||||
return 'An error occurred while reading Outlook email'
|
||||
},
|
||||
}
|
||||
111
apps/sim/tools/outlook/send.ts
Normal file
111
apps/sim/tools/outlook/send.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
import type { OutlookSendParams, OutlookSendResponse } from './types'
|
||||
|
||||
export const outlookSendTool: ToolConfig<OutlookSendParams, OutlookSendResponse> = {
|
||||
id: 'outlook_send',
|
||||
name: 'Outlook Send',
|
||||
description: 'Send emails using Outlook',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'outlook',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Access token for Outlook API',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Recipient email address',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Email subject',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Email body content',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
return `https://graph.microsoft.com/v1.0/me/sendMail`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
// Validate access token
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params: OutlookSendParams): Record<string, any> => {
|
||||
return {
|
||||
message: {
|
||||
subject: params.subject,
|
||||
body: {
|
||||
contentType: 'Text',
|
||||
content: params.body,
|
||||
},
|
||||
toRecipients: [
|
||||
{
|
||||
emailAddress: {
|
||||
address: params.to,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
saveToSentItems: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
let errorData
|
||||
try {
|
||||
errorData = await response.json()
|
||||
} catch {
|
||||
throw new Error('Failed to send email')
|
||||
}
|
||||
throw new Error(errorData.error?.message || 'Failed to send email')
|
||||
}
|
||||
|
||||
// Outlook sendMail API returns empty body on success
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: 'Email sent successfully',
|
||||
results: {
|
||||
status: 'sent',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
// Handle Outlook API error format
|
||||
if (error.error?.message) {
|
||||
if (error.error.message.includes('invalid authentication credentials')) {
|
||||
return 'Invalid or expired access token. Please reauthenticate.'
|
||||
}
|
||||
if (error.error.message.includes('quota')) {
|
||||
return 'Outlook API quota exceeded. Please try again later.'
|
||||
}
|
||||
return error.error.message
|
||||
}
|
||||
return error.message || 'An unexpected error occurred while sending email'
|
||||
},
|
||||
}
|
||||
129
apps/sim/tools/outlook/types.ts
Normal file
129
apps/sim/tools/outlook/types.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { ToolResponse } from '../types'
|
||||
|
||||
export interface OutlookSendParams {
|
||||
accessToken: string
|
||||
to: string
|
||||
subject: string
|
||||
body: string
|
||||
}
|
||||
|
||||
export interface OutlookSendResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
results: any
|
||||
}
|
||||
}
|
||||
|
||||
export interface OutlookReadParams {
|
||||
accessToken: string
|
||||
folder: string
|
||||
maxResults: number
|
||||
messageId?: string
|
||||
}
|
||||
|
||||
export interface OutlookReadResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
results: CleanedOutlookMessage[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface OutlookDraftParams {
|
||||
accessToken: string
|
||||
to: string
|
||||
subject: string
|
||||
body: string
|
||||
}
|
||||
|
||||
export interface OutlookDraftResponse extends ToolResponse {
|
||||
output: {
|
||||
message: string
|
||||
results: any
|
||||
}
|
||||
}
|
||||
|
||||
// Outlook API response interfaces
|
||||
export interface OutlookEmailAddress {
|
||||
name?: string
|
||||
address: string
|
||||
}
|
||||
|
||||
export interface OutlookRecipient {
|
||||
emailAddress: OutlookEmailAddress
|
||||
}
|
||||
|
||||
export interface OutlookMessageBody {
|
||||
contentType?: string
|
||||
content?: string
|
||||
}
|
||||
|
||||
export interface OutlookMessage {
|
||||
id: string
|
||||
subject?: string
|
||||
bodyPreview?: string
|
||||
body?: OutlookMessageBody
|
||||
sender?: OutlookRecipient
|
||||
from?: OutlookRecipient
|
||||
toRecipients?: OutlookRecipient[]
|
||||
ccRecipients?: OutlookRecipient[]
|
||||
bccRecipients?: OutlookRecipient[]
|
||||
receivedDateTime?: string
|
||||
sentDateTime?: string
|
||||
hasAttachments?: boolean
|
||||
isRead?: boolean
|
||||
importance?: string
|
||||
// Add other common fields
|
||||
'@odata.etag'?: string
|
||||
createdDateTime?: string
|
||||
lastModifiedDateTime?: string
|
||||
changeKey?: string
|
||||
categories?: string[]
|
||||
internetMessageId?: string
|
||||
parentFolderId?: string
|
||||
conversationId?: string
|
||||
conversationIndex?: string
|
||||
isDeliveryReceiptRequested?: boolean | null
|
||||
isReadReceiptRequested?: boolean
|
||||
isDraft?: boolean
|
||||
webLink?: string
|
||||
inferenceClassification?: string
|
||||
replyTo?: OutlookRecipient[]
|
||||
}
|
||||
|
||||
export interface OutlookMessagesResponse {
|
||||
'@odata.context'?: string
|
||||
'@odata.nextLink'?: string
|
||||
value: OutlookMessage[]
|
||||
}
|
||||
|
||||
// Cleaned message interface for our response
|
||||
export interface CleanedOutlookMessage {
|
||||
id: string
|
||||
subject?: string
|
||||
bodyPreview?: string
|
||||
body?: {
|
||||
contentType?: string
|
||||
content?: string
|
||||
}
|
||||
sender?: {
|
||||
name?: string
|
||||
address?: string
|
||||
}
|
||||
from?: {
|
||||
name?: string
|
||||
address?: string
|
||||
}
|
||||
toRecipients: Array<{
|
||||
name?: string
|
||||
address?: string
|
||||
}>
|
||||
ccRecipients: Array<{
|
||||
name?: string
|
||||
address?: string
|
||||
}>
|
||||
receivedDateTime?: string
|
||||
sentDateTime?: string
|
||||
hasAttachments?: boolean
|
||||
isRead?: boolean
|
||||
importance?: string
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
import { mistralParserTool } from './mistral'
|
||||
import { notionReadTool, notionWriteTool } from './notion'
|
||||
import { imageTool, embeddingsTool as openAIEmbeddings } from './openai'
|
||||
import { outlookDraftTool, outlookReadTool, outlookSendTool } from './outlook'
|
||||
import { perplexityChatTool } from './perplexity'
|
||||
import {
|
||||
pineconeFetchTool,
|
||||
@@ -183,4 +184,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
microsoft_teams_write_chat: microsoftTeamsWriteChatTool,
|
||||
microsoft_teams_read_channel: microsoftTeamsReadChannelTool,
|
||||
microsoft_teams_write_channel: microsoftTeamsWriteChannelTool,
|
||||
outlook_read: outlookReadTool,
|
||||
outlook_send: outlookSendTool,
|
||||
outlook_draft: outlookDraftTool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user