Понадобилось мне для methodist.io отправлять с google cloud functions приглашения на митинги iCal пользователям. Да так, чтоб как положено: с кнопочками принять и отклонить в Outlook и Gmail. И оказалось, что инфы как это сделать — кот наплакал. Поэтому, не тратя ваше время, перейду к сути.
Сначала нам нужно сгенирировать сам ics файл, а затем отправить его. Для этого я использовал:
- https://github.com/adamgibbons/ics — генерация ics файла
- https://nodemailer.com — smtp отправка
Чтобы календарное событие отображалось в почтовых клиентах так, словно его отправили из outlook или gmail (с кнопочками принять/отклонить и просмотром выбранного тайм слота), нужно отправлять особые хедеры. В них и вся фишка. Именно поэтому нужно использовать SMTP клиент типа nodemailer. В нем можно указывать любой хедер для контента.
Вот примеры функций, которые нам необходимы:
// Генерация ics файла события const ics = require('ics'); function createMeeting(event, organizer, team, eventUrl) { const meeting = { start: moment.utc(event.date).format('YYYY-M-D-H-m').split('-'), end: moment .utc(event.date) .add({ minutes: event.duration }) .format('YYYY-M-D-H-m') .split('-'), startType: 'utc', title: `Meeting title`, description: `Join our online session`, location: 'Online', url: eventUrl, organizer: organizer, attendees: team, method: 'REQUEST', recurrenceRule: event.recurrenceRule, // подробнее можно прочитать в документации ics productId: 'Methodist.io', busyStatus: 'BUSY', }; return meeting; }
// Отправка E-mail с событием через SMTP nodemailer const nodemailer = require('nodemailer'); function postEvent(teamList, organizer, event, meetingFile, roomUrl) { ics.createEvent(meetingFile, (error, icsContent) => { if (error) { console.log(error); } const transporter = nodemailer.createTransport({ host: 'smtp.sendgrid.net', port: 587, secure: false, // true for 465, false for other ports auth: { user: 'YOUR_LOGIN', pass: 'YOUR_PASSWORD', }, }); // send mail with defined transport object transporter .sendMail({ from: 'Sender name <notify@gmail.com>', // sender address to: teamList.map((member) => { return `${member.name} <${member.email}>`; }), // list of receivers subject: `${capitalize(event.type)} session`, // Subject line html: `Join online ${event.type} session. <br> Room link: <a href="${roomUrl}">methodist.io</a>`, // html body, icalEvent: { method: 'REQUEST', filename: 'meeting.ics', content: Buffer.from(icsContent).toString('base64'), encoding: 'base64', }, }) .then((s) => { return 'Success'; }) .catch((err) => { console.log('Emails not sent', err); }); }); }
Если вы хотите использовать не nodemailer, то самое главное – слать контент события с хедером
alternatives: [{ contentType: 'text/calendar; charset="utf-8"; method=REQUEST', content: icsFileContent.toString() }]
И в тексте ics файла method должен быть request. method: 'REQUEST'
Если method будет прописать везде корректно, то событие будет правильно отображаться даже в Outlook.
P.S. Кстати если вы вдруг используете sendgrid, то он поддерживает отправку не только через API, а и по smtp, что позволит делать рассылку с правильно заданным контентом.
const transporter = nodemailer.createTransport({ host: 'smtp.sendgrid.net', port: 587, secure: false, // true for 465, false for other ports auth: { user: 'apikey', // оставляем как есть pass: 'ВАШ_АПИ_КЛЮЧ', }, });
P.P.S Дату в событиях рекомендую использовать в utc формате, чтоб время было корректное в любом часовом поясе.
P.P.P.S Чтоб событие имело правильную продолжительность в аутлуке, нужно указывать не duration, а конечную дату end
.
P.P.P.P.S Если отправлять самому себе на почту gmail iCal событие, то gmail отобразит просто как файл, без кнопок RSVP (accept/decline/tentative etc.) в письме.
Подписывайтесь
Для получения уведомлений о новых публикациях подписывайтесь на мой блог или страницы в соц. сетях: Twitter, Facebook. Также у меня есть канал в Telegram (@golosay_net), в котором я чаще всего публикую что-нибудь интересное про Front-End.