Proper email attachment handling

This commit is contained in:
Roman Krček
2025-06-24 14:49:07 +02:00
parent e2e18d1084
commit be2e22100f
4 changed files with 82 additions and 39 deletions

View File

@@ -1,5 +1,6 @@
import { google } from 'googleapis';
import { env } from '$env/dynamic/private';
import quotedPrintable from 'quoted-printable'; // tiny, zero-dep package
export const scopes = ['https://www.googleapis.com/auth/gmail.send'];
@@ -27,51 +28,73 @@ export async function exchangeCodeForTokens(code: string) {
export async function sendGmail(
refreshToken: string,
{ to, subject, text }: { to: string; subject: string; text: string }
{ to, subject, text, qr_code }: { to: string; subject: string; text: string; qr_code: string }
) {
const oauth = getOAuthClient();
oauth.setCredentials({ refresh_token: refreshToken });
const gmail = google.gmail({ version: 'v1', auth: oauth });
const wrappedHtml = `<!DOCTYPE html>
const message_html =
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${subject}</title>
<link href="https://fonts.googleapis.com/css?family=Lato:400,700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Lato', Arial, sans-serif; margin: 0; padding: 0; background: #f9f9f9; }
.container {
max-width: 480px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
padding: 24px 16px;
word-break: break-word;
}
@media (max-width: 600px) {
.container { padding: 16px 4px; }
}
</style>
</head>
<body>
<div class="container">${text}</div>
</body>
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
</style>
</head>
<body style="font-family: 'Lato', sans-serif; background-color: #f9f9f9; padding: 20px; margin: 0;">
<div style="max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
<p style="white-space: pre-line;font-size: 16px; line-height: 1.5; color: #333;">${text}</p>
<img src="cid:qrCode1" alt="QR Code" style="display: block; margin: 20px auto; max-width: 50%; min-width: 200px; height: auto;" />
<div style="width: 100%; display: flex; flex-direction: row; justify-content: space-between">
<div style="height: 4px; width: 20%; background: #00aeef;"></div>
<div style="height: 4px; width: 20%; background: #ec008c;"></div>
<div style="height: 4px; width: 20%; background: #7ac143;"></div>
<div style="height: 4px; width: 20%; background: #f47b20;"></div>
<div style="height: 4px; width: 20%; background: #2e3192;"></div>
</div>
<div style="font-size: 12px; color: #999; padding-top: 0px; margin-top: 10px; line-height: 1.5; ">
<p>This email has been generated with the help of *insert software name*</p>
</div>
</div>
</body>
</html>`;
const message = [
`To: ${to}`,
'Content-Type: text/html; charset="UTF-8"',
'Content-Transfer-Encoding: 7bit',
`Subject: ${subject}`,
'',
wrappedHtml
].join('\n');
const raw = Buffer.from(message).toString('base64url');
const boundary = 'BOUNDARY';
const nl = '\r\n'; // RFC-5322 line ending
const htmlQP = quotedPrintable.encode(message_html);
const qrLines = qr_code.replace(/.{1,76}/g, '$&' + nl);
const rawParts = [
'MIME-Version: 1.0',
`To: ${to}`,
`Subject: ${subject}`,
`Content-Type: multipart/related; boundary="${boundary}"`,
'',
`--${boundary}`,
'Content-Type: text/html; charset="UTF-8"',
'Content-Transfer-Encoding: quoted-printable',
'',
htmlQP,
'',
`--${boundary}`,
'Content-Type: image/png',
'Content-Transfer-Encoding: base64',
'Content-ID: <qrCode1>',
'Content-Disposition: inline; filename="qr.png"',
'',
qrLines,
'',
`--${boundary}--`,
''
];
const rawMessage = rawParts.join(nl);
const raw = Buffer.from(rawMessage).toString('base64url');
await gmail.users.messages.send({
userId: 'me',